New Facebook Photo Hacks

Last March, Facebook caught some flak when some hacks circulated showing how to access private photos of any user. These were enabled by egregiously lazy design: viewing somebody’s private photos simply required determining their user ID (which shows up in search results) and then manually fetching a URL of the form:
www.facebook.com/photo.php?pid=1&view=all&subj=[uid]&id=[uid]
This hack was live for a few weeks in February, exposing some photos of Facebook CEO Mark Zuckerberg and (reportedly) Paris Hilton, before the media picked it up in March and Facebook upgraded the site.

Instead of using properly formatted PHP queries as capabilities to view photos, Faceook now verifies the requesting user against the ACL for each photo request. What could possibly go wrong? Well, as I discovered this week, the photos themselves are served from a separate content-delivery domain, leading to some problems which highlight the difficulty of building access control into an enormous, globally distributed website like Facebook.

Here’s an example “public link” of a photo of me in my office:

http://www.facebook.com/photo.php?pid=34947682&id=210132

Posting this link shouldn’t be a privacy problem, as you shouldn’t be able to see a photo by following this link unless you’re in my network of friends. Facebook promotes this view by telling you at the bottom of the page that you can safely send this link out to friends, and in fact such links are posted all over the web if you search for them. Access rights are enforced on the Facebook page, so knowing this link doesn’t reveal the photo. But, unfortunately, the actual photo file is embedded in the page with the address:

http://photos-c.ak.fbcdn.net/photos-ak-sf2p/v646/41/83/210132/n210132_34947682_4899.jpg

Presumably, ‘fbcdn.net’ stands for ‘Facebook Content Delivery Network,’ and the image is hosted here using a high-performance photo server which doesn’t have to do all the session management overhead of the larger site. Keep in mind that, as reported in October, Facebook is hosting 10+ billion photos, by some measures more than any other site on the web. If the URL of a photo were temporary and difficult to guess from the public address, this scheme might be okay. The photo server will in fact respond to a request from wget without any cookies at all. It has to, because it is in a different domain than the main Facebook site, and browsers are specifically designed to prevent transferring state between domains.

Unfortunately the link is neither temporary nor difficult to guess. The links appear to work indefinitely, based on trying some months-old ones floating around the web. Worse, most of the apparent randomness in the URL is not needed to access the photo. The following link is just as valid as the one posted above:

http://photos-c.ak.fbcdn.net/photos-ak-sf2p/210132/n210132_34947682_4899.jpg

All we need is the actual filename of the photo, and I’ve reverse-engineered the filename format as:
[photo-size][uid]_[pid]_[PIN].jpg
Photo-size is just a character in the set {t, s, n} representing the resolution of the image, uid is the user ID of the user who uploaded the photo, pid is a photo ID, and PIN is a four-digit random number. I’m calling it a PIN because it was chosen to be four decimal digits, which can only be assumed to have been done in a foolish analogy to bank card security. It’s easy to learn everything but the PIN given a public link to the photo. Brute-forcing the PIN is also fairly easy: it’s a space of 9000, which can be searched in about 45 minutes using one script. This is also easily parallelisable, given that we can query any of the mirrored photo servers in the set {photos-a.ak.fbcdn.net, photos-b.ak.fbcdn.net … photos-z.ak.fbcdn.net} we can get this down to under 2 minutes.

This is still a lot of work for one photo, but it gets better. Incrementing the photo ID by one reliably gives the next photo that was uploaded as part of the same album. Looking at the next few photos in sequence from the one posted above, the sequence of PINs is {4899,5210,5535,5857,6193,6524,6853}, giving deltas of {311,325,322,336,331,329}. These are almost certainly created by timestamping as the photos are received. So, given the public link to one photo, and doing one brute-force, we can pretty easily get the rest of the album with 10-20 queries per photo. I’ve coded this up and it works splendidly–the photo servers don’t appear to do any rate-limiting or blocking.

How to fix this problem? Obviously Facebook could check the session cookies for every photo request, but we’ll assume this is impractical given the current setup. If concede that using the knowledge of an opaque URL as a capability to view a photo is all we have to work with, then there is no reason not to increase the length of the PIN portion to be a cryptographically-strong 20 digits–it doesn’t need to ever be written or stored by a human. Of course, these must be generated randomly as photos are uploaded. It would also be prudent to have the PINs expire after an hour or so, as they aren’t meant to provide a permanent link, and may end up cached in all sorts of places. Finally, multiple requests with invalid PINs should lead to IP blocking to prevent crawling.

This is a smaller hole than the one from last year, as we need to find a public photo link first. As far as I can see, there’s no predictable pattern of photo IDs for given user IDs, so we can’t access photos for our arbitrary choice of user. Still, it is a privacy violation as Facebook promotes the view that public links won’t allow access to photos, when they actually do. Above all, it is an inexcusably sloppy design, especially given the bad press Facebook received for the original problems.

69 thoughts on “New Facebook Photo Hacks

  1. Note the giveaway “ak.” – fbcdn is indeed a CDN, but it’s actually Akamai Technologies’s CDN.

    1 192.168.1.1 (192.168.1.1) 1.578 ms 1.871 ms 0.726 ms
    2 ar0.rbsov.bogons.net (193.178.223.245) 26.429 ms 29.046 ms 29.954 ms
    3 cr0-Vl1455.thdo.bogons.net (194.39.143.161) 28.916 ms 26.863 ms 26.478 ms
    4 cr0-G1-1.rbsov.bogons.net (193.178.223.218) 26.052 ms 25.778 ms 24.490 ms
    5 linxnap.netarch.akamai.com (195.66.224.168) 27.059 ms 28.777 ms 31.115 ms
    6 a92-122-208-200.deploy.akamaitechnologies.com (92.122.208.200) 30.019 ms 28.898 ms 26.221 ms

    Presumably they just offload all the images into the akamai instances and hope. To do better, they might need to move to something like Akamai Edge Computing, where they push app servers out to the edge – so they could serve images as if they were directly served from facebook.com, but at the edge.

  2. Good observation–I suspected the use of a separate domain name for the photos is to enable the use of a third-party CDN. Curiously, some of my photos on the site are hosted within the facebook.com domain, seemingly those which I uploaded from within the USA:

    http://photos-a.ll.facebook.com/photos-ll-sf2p/v74/41/83/210132/n210132_32194048_4702.jpg

    Interestingly, it is much pickier about the URL, requiring the directory structure before the filename to be correct and making this attack more difficult (though it’s not clear this is meant as a security feature or is just an accident).

    Perhaps we are observing growing pains as Facebook uses Akamai and other third-party CDN’s to host it’s photos for Europe, where it doesn’t have it’s own server farms?

  3. LL = Limelight Networks, surely?

    1 192.168.1.1 (192.168.1.1) 1.487 ms 0.727 ms 0.728 ms
    2 ar0.rbsov.bogons.net (193.178.223.245) 25.536 ms 27.323 ms 27.866 ms
    3 cr0-Vl1455.thdo.bogons.net (194.39.143.161) 37.859 ms 34.327 ms 30.837 ms
    4 tge2-3.fr4.lon.llnw.net (195.66.226.133) 23.448 ms 32.701 ms 25.258 ms
    5 ve5.fr3.lon.llnw.net (69.28.171.137) 25.289 ms 37.723 ms 41.875 ms
    6 tge7-2.fr3.lga.llnw.net (69.28.171.125) 97.004 ms 93.573 ms 93.150 ms
    7 cdn-208-111-128-6.lga.llnw.net (208.111.128.6) 94.873 ms 125.412 ms 93.998 ms

    Interesting that they’re using multiple CDNs.

  4. SmugMug ran into an issue like this not long ago. It was actually pretty easy to access random pictures (you could do it by hand, didn’t even need a script), even for albums that were considered “private”. They ignored the problem until the bloggers discovered the loophole, finally generating enough attention (and negative publicity) so that SmugMug had to fix it.

  5. How is the photosize easily obtained from the original URL? I mean, certainly photos come in some standard sizes, but they don’t need to. One can crop something and upload the crop. So wouldn’t your brute force need to also guess the photo size for all PINs?

  6. Interesting!

    “This is a smaller hole than the one from last year”. There will always be a work around to a fix from facebook.

  7. They should learn from myspace photos. The file names consist of a letter denoting the size, and a hash of an unknown combination of variables, which probably include photoID, UID, albumID, timestamp, etc.

    The only way to get the URL to the photo is to visit the album, which is protected by privacy settings. And if the actual .jpg URL is shared, there is no personally identifiable or easily generated information in the filename

  8. Richard Wagnerius,

    You could always use Cygwin.

    Porting to Python would also be trivial — I might do this in the next couple of days… (I was going to update the Google Code page with a more practical example soon anyway.)

  9. Apparently things changed for PIN.

    Just uploaded a serie of photos today, the first is :

    http://photos-a.ak.fbcdn.net/photos-ak-snc/n688128920_1416990_508624.jpg

    and for the other :
    n688128920_1416990_508624
    n688128920_1416991_5698379
    n688128920_1416992_419399
    n688128920_1416993_6452287
    n688128920_1416994_1876604
    n688128920_1416995_5456914
    n688128920_1416996_3920588
    n688128920_1416997_1648475
    n688128920_1416998_3840230
    n688128920_1416999_1262169
    n688128920_1417000_7373886
    n688128920_1417001_2291228
    n688128920_1417002_4354333
    n688128920_1417003_3309571
    n688128920_1417004_8117456
    n688128920_1417005_5151293
    n688128920_1417006_4690735
    n688128920_1417007_5531666
    n688128920_1417008_1689620
    n688128920_1417009_6540206

    Photos have been uploaded at «the same time», I mean in the same group of photos using «upload all» in the java applet.

    I don’t see any timestamp here, it looks like 7 decimal random digits.

    I’ll really slow the process for brute force 🙁

    Hopefully, we always can check babe’s photos that have been uploaded before that filename change.

  10. brunetton,

    In your example, the total number of digits is 22, as opposed to just 18 in the article. So, brute forcing your photos would theoretically take 10,000 times as much effort.

    However, in practice, “UID” is still constant, and “PID” are still sequential, so brute-force attacks (given a single known URL to begin with) remain entirely plausible.

  11. v4lkyrius, indeed it remains possible, but it’ll be 1000x slower (minimum) :
    4 digits (with a small delta) –> 7 random digits

    It was just what I wanted to point

  12. Sorry not a programmer but need to know if you do consider Facebook privacy secure. I am searching answers to advise if this is the right tech tool to use for teaching professionals who wish to communicate with other teaching professionals on a variety of topics (teacher unions advise against it).

  13. i’m trying your example v4lkyrius, my url_list now is 14GB and i’ve restricted the search from PID_MIN (00000000) to PID_MAX (01999999)… and it is still running!!

  14. @11
    actually, you can’t use an hash as PIN, because hash is (should) be irreversible, while the server needs a quick way to associate a number to a filename. If hash were a feasible solution, it would be so easy to use just an MD5 for every file like [size][md5].jpg.

    I.e. given an md5 of the image, you can’t go back to the image/filename quickly. It would require a database to associate every hash to every file in one single enormous table.

    In addition, hashes aren’t so quick to generate. This is an high traffic server, i guess there are performance reason because they didn’t use an hash from the beginning.

  15. is this still working??
    I see that the UID currently is more than 6 digits… so is this still working?

  16. @v4lkyrius

    hello v4lkyrius
    is your code still working?
    I am not able to work on cygwin xargs
    it says command not found

    please help

  17. so if I had some picture ids from a past friend whose albums are set to private how would I pull those up now?

    what would I put into those links to change it to the pic I want to see?

    thanks for help

  18. The script still “works” with the longer UID and random 7-digit PIN, it’s just not practical anymore given the crazy size of the url_list.txt you are going to get. If it could be modified to create the URL, do the wget and get whatever is there to be gotten, and then move on to the next URL without creating that massive text file, that would be great. Could just leave it running forever.

  19. Poking around with this today. A few things: Facebook has moved to the larger numbers for new uploads, but this method is still practical for older files.

    However, when I try to use it, wget winds up eating up all my RAM and bringing my system to a grinding halt quickly. Is there a way to avoid this?

  20. Is there a way to determine a PHOTO-ID for an unknown gallery given you have acess to one or more urls to different galleries?

  21. Or simply, you can guess a friends password…. Most of them are using the ddmmyy format… Works every time…

  22. I don’t care how long the PIN is; it’s still technically a security issue. If they’re using a 3rd party CDN with no intelligence aside from wget, that means that a “friend” can grab the true URL of a photo, post it somewhere, and that’s that.

    People are all caught up on the idea that a photo is secure, even on a CDN, as long as the URL is “unguessable”. But isn’t anyone concerned about an unguessable URL being acquired legitimately, but then posted somewhere it shouldn’t be, at which point there is no ACL to check permissions?

  23. Hey, could you figure out who uploaded the photo which has a filename ending in 13523613_3602037.jpg? I think it’s from an album, not a profile picture, hence it has a 5-string ID but I was only given two of the strings. I don’t know the user ID of the person (that’s what I’m trying to figure out). Any advice would be appreciated. Thanks.

  24. Is there a way to figure out the sequence of the pin? For example, one of my pins (as you identify it) is .1273.

    If someone has that pin along with the [photo-size][uid]_[pid]_[pin], can they now view my other photos? Thanks!

  25. I believe the script needs to be updated… as for there for the digits have been increased and extra numbers have been included…

    please kindly lend us the updated version

  26. For the fbcdngen script, how large is the text file supposed to be when you run with the old 4-digit pins? I had to stop mine because it was approaching 400 gigabytes (after I left the computer for a few hours) and was going to fill up my entire hard drive if I let it run much longer. I don’t know if it was looping infinitely or if those really are all individual values — if the latter, how does anyone have the hard drive space for the 7-digit pins..?

  27. Reference the post regarding the link:

    http://photos-a.ak.fbcdn.net/photos-ak-%5Banything%5D/

    I was in the middle of experimenting with this URL, when my connection dropped out while the page was loading, leaving me with something quite interesting in my address bar. I had:

    http://www.zwunzi.com/%5Band a huge random sting]

    I think this may be of interest. Are Facebook hosting phots on Zwunzi.com?

    The Zwunzi URL is not in my browser history, almost as though it never happened. Otherwise I would have posted the actual link here.

  28. too bad ugly51 that seems like a virus not any kind of photo hosting on the part of facebook.
    a quick google for zwunzi will tell.

  29. Is it possible to use this method to retrieve a recently deleted photo? The album’s open and stuff. I basically have everything but the pin and the server. They seem to be different from picture to picture although they’re in the same album.

    Thanks in advance!

  30. Hey Joseph (and others),

    Take note of the direct link to images you’re ready to delete off of Facebook, delete them and check a few days later.

    If they’re still there, send them a message through the non-copyright infringement notice page with a list of those direct links.

    So far, I’ve been told it works.

    If enough people do it, then Facebook will have to address their privacy concerns before people quit and move to some of the newer sites opening up.

  31. It didn’t work! Help!!

    All I have is the user’s URL, I can’t even view the pictures… I tried copy pasting, that didn’t work either!

  32. It didn’t work! Help!!

    All I have is the user’s URL.. I can’t view the pictures.. I tried copy and paste, that didn’t work either!!

  33. hi ,
    please i want to know how can i search by photo in facebook?? or in tagged or my space or any site like that???
    i want to know if there is some one use my photos in a fake profile
    please i need your help

  34. im confuse! can someone teach me, how to get the photo Id and the pin which has the 7 digit numbers. thank you

  35. Hi,

    It seems that fbcdn filenames format has changed, and now looks like that :
    ID1_ID2_ID3_ID4_ID5_SIZE.jpg

    ID3 seems to be uploader’s ID, SIZE is in the same set as before.

    Did anyone worked on reversing this new format ?

  36. Hi,

    I think my fiancee is hiding something from me. In other words, sleeping with another guy. Her profile weblink is:

    http://facebook.com/nmsisg
    her id=1032585243
    Massiel Galan

    I need help from someone that can help me view this profile. I know she is hiding something from me… I just need to verify.
    Please help! I am lost…

    Gare

  37. With the actual links on fbcdn, the photos have now more IDs.
    [ID1]_[PID]_[UID]_[ID4]_[ID5]_[SIZE].jpg

    PID = photo id;
    UID = user who uploads the photo;
    SIZE = the size in the picture, could be q, n, o and merely t, s…
    …but what about the others ID?!?!
    It looks like they want to make harder the way of getting those IDs, but we can get access to the pictures if knowing the URL.

  38. Good post however , I was wanting to know if you
    could write a litte more on this subject? I’d be very grateful if you could elaborate a little bit more. Appreciate it!

  39. I am not sure where you are getting your information,
    but good topic. I needs to spend some time learning more or understanding more.

    Thanks for magnificent information I was looking for this info for my mission.

Leave a Reply to Arkhonx Cancel reply

Your email address will not be published. Required fields are marked *