6

I need to prevent from directory traversal attack from my code using Python. My code is below:

if request.GET.get('param') is not None and request.GET.get('param') != '':
    param = request.GET.get('param')
    startdir = os.path.abspath(os.curdir)
    requested_path = os.path.relpath(param, startdir)
    requested_path = os.path.abspath(requested_path)
    print(requested_path)
    tfile = open(requested_path, 'rb')
    return HttpResponse(content=tfile, content_type="text/plain")

Here I need user is running like http://127.0.0.1:8000/createfile/?param=../../../../../../../../etc/passwd this it should prevent the directory traversal attack.

halfer
  • 19,824
  • 17
  • 99
  • 186
satya
  • 3,508
  • 11
  • 50
  • 130
  • user should not be allowed to access or modify sudo directories. So check that path is not a sudo dir – Arpit Solanki Jul 19 '17 at 11:09
  • I need to prevent also that. Can you make it like this ? – satya Jul 19 '17 at 11:11
  • not saying that its a good solution but there are around 20 root directories so check that path does not contain any one of them like if user requests a path containing bin directory then don't allow – Arpit Solanki Jul 19 '17 at 11:15

3 Answers3

11

Suppose the user content is all located in

safe_dir = '/home/saya/server/content/'

Ending with / is important as heinrichj mentions to ensure the check below matches against a specific directory.

You need to verify the final request is in there:

if os.path.commonprefix((os.path.realpath(requested_path),safe_dir)) != safe_dir: 
    #Bad user!

If the requested path is allowed to be the save_dir itself, you would also need to allow entry if os.path.realpath(requested_path)+'/' == safe_dir.

I encourage you to make sure all stuff you want accessible by the user in one place.

kabanus
  • 24,623
  • 6
  • 41
  • 74
  • 2
    You should consider adding `os.path.realpath()` to it in case there is a symlink in the safe path that points outside of it. – zwer Jul 19 '17 at 11:16
  • @satya Correct. It's usually the best practice to make sure stuff readable by the world is in the same place. – kabanus Jul 19 '17 at 11:17
  • Ok,Let me to implement it and let you know. – satya Jul 19 '17 at 11:19
  • Can I write `safe_dir=os.path.abspath(os.curdir)`, If i need to give permission to my projrct folder only. – satya Jul 19 '17 at 11:20
  • @satya No, because you may be executing from somewhere else. If the python file is in the 'safe' directory use `safe_dir=os.path.realpath(__FILE__)` though I usually separate content from code. Also note you should use **realpath**, as zwer mentioned. – kabanus Jul 19 '17 at 11:22
  • `safe_dir` is giving `/opt/lampp/htdocs/Nuclear_reactor/d50/nuclear_correct/__FILE__` like this. Is this right ? – satya Jul 19 '17 at 11:28
  • @satya I had a typo - forgot to get the dir, and name of variable: `safe_dir=os.path.dirname(os.path.realpath(__file__))` – kabanus Jul 19 '17 at 11:28
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/149584/discussion-between-satya-and-kabanus). – satya Jul 19 '17 at 11:29
  • This doesn't seem watertight, because `commonprefix` just looks for the longest *string* prefix. If you allow access to `/web`, this will also allow traversal to `/web2`, for example. – heinrichj Oct 17 '19 at 09:50
  • And years afterwards, a security flaw is found. @heinrichj thanks! added a fix. – kabanus Oct 17 '19 at 10:09
  • @kabanus no problem. You might consider using `startswith` instead, which *does* look at full path components. – heinrichj Oct 17 '19 at 12:48
  • @heinrichj I am unfamiliar with that, except as a string function (which acts like `commonprefix`). I do not think it's from a standard library. I'll add it to the answer if you tell me where to find it. – kabanus Oct 17 '19 at 18:00
8

you could try the methods of pathlib.Path

Path(root_dir).joinpath(param).resolve().relative_to(root_dir.resolve())

should return the relative path starting from the root_dir, or raise an ValueError if a directory traversal attack is tried

testing

param = 'test_file'
Path(root_dir).joinpath(param).relative_to(root_dir)

WindowsPath('test_file')

param = 'test_file/nested'
Path(root_dir).joinpath(param).relative_to(root_dir)

WindowsPath('test_file/nested')

param = 'non_existing/../../data'
Path(root_dir).joinpath(param).resolve().relative_to(root_dir.resolve())
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-26-a74379fe1817> in <module>()
....
ValueError: 'C:\\python_scripts\\PyCharmProjects\\data' does not start with 'C:\\python_scripts\\PyCharmProjects\\testproject'
param = 'non_existing/../nested'
Path(root_dir).joinpath(param).resolve().relative_to(root_dir.resolve())

WindowsPath('nested')

Maarten Fabré
  • 6,938
  • 1
  • 17
  • 36
-1

a check like below will also prevent traversal.

if '..' in pathParam:
    abort(ERRORCODE)
isubodh
  • 45
  • 7