LFI + /proc shenannigans

Yo! Long time no see. This time i’ll be talking a bit about how to expand your LFI possibilities :slight_smile:

Wikipedia defines Local File Inclusion (LFI) as a type of web vulnerability that is most commonly found to affect web applications that rely on a scripting run time. This issue is caused when an application builds a path to executable code using an attacker-controlled variable in a way that allows the attacker to control which file is executed at run time. It’s worth mentioning this vulnerability usually takes advantage of another vulnerability type called Path Traversal, in which an attacker can traverse the directory tree, gaining unauthorized access to file system.

A simple example

Imagine the following scenario: While performing an assessment, you come across a web application that allows the user to read logs, providing the ability to properly format outputs as you wish. The user can highlight specific information, exclude rows based on filters, so on and so forth.

By poking with the app, eventually you notice the following request flying over the proxy:

GET /?filename=10122023.log&v=1 HTTP/1.1

Host: http://app.company.com
Origin: http://app.company.com
...additional headers

Naturally, you decided to replace 10122023.log with something else, maybe something along the lines of test.log. As expected, you get a 404 response from the server, so you know the application actually did look the file up, it just couldn’t find it. You get all psyched and immediately throw in a ../../../../etc/passwd in there, and you get the following response back:

HTTP/1.1 200 OK

access-control-allow-origin: *
referrer-policy: strict-origin-when-cross-origin
content-type: application/json; charset=utf-8
Server: Express

list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
systemd-resolve:x:996:996:systemd Resolver:/:/usr/sbin/nologin
tss:x:104:109:TPM software stack,,,:/var/lib/tpm:/bin/false

Jackpot. You are in business.

That’s cool and all, but maybe you want to continue exploiting the application to see how much more you can squeze out of it. Maybe some environment variables? Maybe some hardcoded credentials? The sky is the limit.

The problem is, you put an index.js in there and nothing comes back. You then, try app.js. Also nothing. What? You know it’s a nodejs based app because, being the recon master you are, you couldn’t ignore that Server: Express when first toying around with requests. So as a last resource, you put together a list of possible files and brute force you through just to get to the end of the list with empty hands. WTF did this developer do that he’s not following file naming standards?

So, dead end? You better than that.

The /proc directory

The /proc directory contains (among other things) one subdirectory for each process running on the system, which is named after the process ID (PID). Consider the following:

@-box:~$ sleep 20&
@-box:~$ ps -ef | grep "sleep"
user 233322 0.0 0.1 5656 1920 pts/0 S 11:18 0:00 sleep 10

You call sleep 20, which is gonna hang the process for 20 seconds, while sending it to the background with &. Then, you call ps -ef, which select all processes, while filtering by “sleep”. This gives you the PID which is 233322. Now you do:

@-box:~$ cat /proc/233322/cmdline # and it returns "sleep 20"

Makes sense?

Another interesting thing is that, this directory has a link to ‘self’, which points to the process currently reading the file system. For instance, if you are sitting on a terminal with your bash session open, and you type cat /proc/self/cmdline, you would get -bash in return, because this, as mentioned before, is the current process reading the file system.

Dumping the source code

But going back to the LFI pinnacle: Can you see where we going with this? If you do replace /etc/passwd/ with /proc/self/cmdline, you should get the command line arguments that spun the process up, maybe something like node /home/bob/log-app/my-server.js. That would lead you to the entry point file, so you could happily start dumping the source code.

Dumping environment variables

'Member when I mentioned environment variables? They usually hold the keys to the castle and having access to it, drastically increases our chances of walking away with something valuable. However, can we get it without achieving a proper RCE? Well, yeah. Turns out, there’s an entry for that too under /proc. All you have to do is setting your payload to /proc/self/environ and whatever environment variables the current process has access to, should be printed out like magic. Isn’t that pretty?

Getting the parent process ID (PPID)

There is always a possibility of the application being spawned by another application. In the case of python web apps, for instance, Uvicorn could be one of the daemons responsible for keeping it up and running. It establishes itself as the parent process in relation to the web application. Having said that, is possible to enumerate that as well, by dumping the file /proc/self/stat. The outcome is similar to what can be seen below:

164805 (node /home/bob/log-app) S 17612 164805 164805 0 -1 4194304 257091390 30537 33 8 531043 128694 74 67 20 0 11 0 170196020 675442688 11160 18446744073709551615 94626273062912 94626273075221 140735478764848 0 0 0 0 16781312 17922 0 0 0 17 0 0 0 0 0 0 94626273086872 94626273087504 94626292690944 140735478768032 140735478768097 140735478768097 140735478771690 0

For the purposes of this section, we only care about the 17612, as it represents the PPID. Knowing that, you can now go through all the steps we discussed up to this point given the file system is identical regardless of a process being parent or child.

Wrapping up

Hope this sheds some light in helping you to level up your LFI game. I obviously would be very much interested in hearing your approach as well, so share away.

See you next time.



/proc/ indeed is very juicy, i just ran a strace on ps (it mostly reads /proc/), imagine implementing ps but using the lfi vector. (i know,i know, it sounds crazy)

1 Like

Hi, nice post! Interesting how you can use the /proc file system to obtain potentially sensitive information, this is something I hadn’t considered with LFI. One aspect I did not see you address are file system permissions. This could throw a wrench in exploiting an LFI vulnerability if the web application is running as a user with little permissions because it can prevent you from reading files such as /etc/passwd and information of other processes under /proc. Have you encountered this limitation in practice, and how would you deal with it?

1 Like

@Noswis that’s a very good point. I know for instance, if you mount /proc with hidepid, reading from /proc wouldn’t be possible and therefore, that trick wouldn’t do. Since I mostly found this in situations where www-data (user associated with the server process) had pretty standard permissions, it worked like a charm.

In terms of recommendation, I would maybe try another route. Considering you know you have LFI (i usually try very unimportant files first before the files i want, just to wrap my head around possible user permissions) maybe try poisoning log files, if the app is running nginx / php or apache / php combo.

Maybe @messede can contribute with some more in here :slight_smile:


1 Like

in /proc’s case many things are world readable (including the cmdline of root processes), you can find password and secrets being passed in cmdline of certain process. Usually the first thing anyone should go for in case of LFI is : identify the web root of the application, and then proceed to obtain the source code of the app, this massively expands your knowledge of target app and opens up new attack surfaces. (all this is ofcourse web 101, i dont think im adding much to the conversation)


This topic was automatically closed after 121 days. New replies are no longer allowed.