Brute Force(暴力破解)
我们知道暴力破解密码其实本质就是暴力穷举,说白了就是一个一个试,这里我们主要是使用bp的抓包intruder进行爆破
low
首先我们随便输入一个数
发现有报错,我们首先观察一下源码
发现只是简单的对我们传进去的username和password与后端数据库进行对比,并未对登录进行限制所以甚至可以使用SQL注入的万能密码进行登录。(根据源码用SQL注入闭合掉users前面的引号并且对后面的sql语句进行注释掉,因为我们知道admin是正确的用户名,所以sql语句中判断完admin为正确user而且后面的语句被注释所以通过了这条sql语句查询)
也可以用爆破方法,先bp抓包放入intruder,配置好参数对password进行爆破
通过爆破发现密码为password可以登录
medium
可以观察到medium难度对输入的字符串进行了筛查,mysql_real_escape_string这个函数用于对字符串进行转义,以防止SQL注入攻击。它会在特殊字符前面加上反斜杠,使得这些字符不能在SQL语句中被识别为特殊命令,所以我们不能使用sql万能密码进行登录。并且登录失败后会sleep(2),爆破会慢一点,但是同样并无大碍,同上步骤爆破得到password并且登录成功
high
我们发现源码中加了一个user_token,所以需要我们把password token 2个参数同时提交,并且将bp调整至pitchfork,它可以使用多组Payload集合,在每一个不同的Payload标志位置上(最多20个),遍历所有的Payload
选择Grep-Extract,意思是用于提取响应消息中的有用信息,点击Add,如下图进行设置,框选需要的user_token的内容,最后将Redirections设置为Always,总是进行重定向
在设置payload,注意在下面initial payload for first request输入第一次设置时的token
最终破解出密码
impossible
<?php
if( isset( $_POST[ 'Login' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Sanitise username input
$user = $_POST[ 'username' ];
$user = stripslashes( $user );
$user = mysql_real_escape_string( $user );
// Sanitise password input
$pass = $_POST[ 'password' ];
$pass = stripslashes( $pass );
$pass = mysql_real_escape_string( $pass );
$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 = $row[ 'last_login' ];
$last_login = strtotime( $last_login );
$timeout = strtotime( "{$last_login} +{$lockout_time} minutes" );
$timenow = strtotime( "now" );
// Check to see if enough time has passed, if it hasn't locked the account
if( $timenow > $timeout )
$account_locked = true;
}
// 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();
?>
观察源码发现 Impossible级别的代码加入了可靠的防爆破机制,当检测到错误登录3次后将会锁定账户15s,系统会将账户锁定,爆破也就无法继续,同时采用了更为安全的PDO(PHP Data Object)机制。我们无法进行爆破
Command Injection(命令注入)
命令注入(又叫操作系统命令注入,也称为shell注入)是指在某种开发需求中,需要引入对系统本地命令的支持来完成某些特定的功能。当未对可控输入的参数进行严格的过滤时,则有可能发生命令注入。攻击者可以使用命令注入来执行系统终端命令,直接接管服务器的控制权限。它允许攻击者在运行应用程序的服务器上执行任意操作系统命令,并且通常会完全破坏应用程序及其所有数据。
low
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];
// 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>";
}
?>
我们用ping命令来ping一下ip,输入127.0.0.1&&ifconfig(由于我是kali的dvwa所以要用liunx的查询ip命令)
发现出现回显,这时我们就可以在此植入恶意代码等来对网站进行攻击等等
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>";
}
?>
发现只是单纯的将&&和;进行过滤,那我们输入127.0.0.1&ifconfig
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>";
}
?>
过滤了好多符号,但是通过仔细观察发现‘| ’这里面是|加一个 空格,并没有过滤|单独的符号
输入127.0.0.1|ifconfig即可
impossible
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$target = $_REQUEST[ 'ip' ];
$target = stripslashes( $target );
// Split the IP into 4 octects
$octet = explode( ".", $target );
// Check IF each octet is an integer
if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) {
// If all 4 octets are int's put the IP back together.
$target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];
// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// Feedback for the end user
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难度采用的防御机制:
1.采用了跟暴力破解一样的token机制,每次客户端与服务端交互都为随机的
2.stripslashes()函数:过滤字符串中的反斜杠
3.explode()函数:将所有的字符串打撒成为数组
4.is_numeric() 函数:用于检测变量是否为数字或数字字符串
所以我们跟本无法对此进行命令注入,也无法传入一句话木马等获取shell
CSRF(Cross Site Request Forgery 跨站请求伪造)
跨站请求伪造(Cross Site Request Forgery,CSRF)是一种攻击,它强制浏览器客户端用户在当前对其进行身份验证后的Web 应用程序上执行非本意操作的攻击,攻击的重点在于更改状态的请求,而不是盗取数据,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。攻击者可以伪造当前已经登录用户的身份访问正常的网站,执行非本意的操作。正常的网站,没有对来源请求进行严格的验证和过滤,导致攻击者可以伪造正常用户的请求,达到攻击目的。
low
可以看到这里的csrf体现在更改密码的环节,这里观察源码
<?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 = mysql_real_escape_string( $pass_new );
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' );
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
mysql_close();
}
?>
可以看到源码这里是一个很简单的一个对比原始密码和修改后密码的判断,并无任何防csrf的措施,只有一个mysql_real_escape_string用来防止sql注入来干扰对后台数据库的比对(这里实际是对请求的发送者有一个cookie的验证的,代码并未体现,所以在后面的步骤中要用同一浏览器打开)
我们修改密码
发现参数出现在url中,判断为get型传参,修改成功 ,复制地址在同一浏览器重新访问,我们可以通过前面的爆破模块进行测试,发现密码已经被更改
在真实的攻击中,攻击者通常会利用这一点,在你登录一网站账号,并且身份验证并未过期时,诱导你打开新的链接(攻击者提前准备好的恶意链接),修改你的密码等
在这里我们可以延申一下对这个漏洞的利用,主要方法有:
1.构造恶意链接(通常会很明显要用到短链接生成一个看起来安全的连接)
2.构造恶意页面(可以构造一个恶意的html页面)
3.结合存储型xss使用
medium
我们发现仍旧是get型,可以修改密码
观察源码
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Checks to see where the request came from
if( eregi( $_SERVER[ 'SERVER_NAME' ], $_SERVER[ 'HTTP_REFERER' ] ) ) {
// 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 = mysql_real_escape_string( $pass_new );
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</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>";
}
mysql_close();
}
?>
这里多了一个对比,首先是eregi()函数在一个字符串搜索指定的模式的字符串,搜索不区分大小写。通过抓包我们可以观察到包中多了referer,这里源码的意思其实就是对referer和前面host中的ip进行对比,只要referer中出现host中的ip才能继续进行下去
high
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF 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 = mysql_real_escape_string( $pass_new );
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' );
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
mysql_close();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
可以看到high难度的csrf加入了token的限制,token都是就随机的,存在一个对token的检查,正确才可以继续修改密码。
这里有个跨域问题,指的是浏览器不能执行其他网站的脚本,由于同源策略的影响,即浏览器对javascript施加的安全限制,当从一个域名的网页去请求另一个域名的资源时,就无法成功获取资源,这时候需要有跨域才能成功获取资源。解决这个问题的其中一个办法就是利用JSONP
原理就是利用<script>标签没有跨域限制,通过<script>标签的src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。缺点是:只能发送get一种请求
由于这个难度还是get传参,所以实际我们照样可以利用恶意页面去获取用户的token
<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>
我们可以在恶意页面的源码中加入这段代码即可取得用户的token,并在跳转到我们后续修改密码的页面时取用这段token进行验证
或者我们可以使用bp中有个插件CSRF Token Tracker
在这里填上正确的host和name,注意这里后面要填的是第一次抓到的token然后发送到repeater
然后发送就可以看到密码修改成功了
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 = mysql_real_escape_string( $pass_curr );
$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 = mysql_real_escape_string( $pass_new );
$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();
?>
观察代码发现利用了PDO机制防止sql注入,同时添加了必须输入原始密码才可以修改密码,攻击者在不知道原始密码的情况下无法修改密码
File Inclusion(文件包含)
当服务器开启allow_url_include选项时,就可以通过PHP的某些特性函数(include(),require()和include_once(),requir_once())利用URL去动态包含文件,此时如果没有对文件来源进行严格审查,就会导致任意文件读取或者任意命令执行
本地文件包含LFI(local file include)当被包含的文件在服务器本地时,就形成本地文件包含
远程文件包含RFI(remote file include)当被包含的文件在第三方服务器时,叫做远程文件包含
(这里只做了本地文件包含的测试)
low
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
?>
观察这里并未有任何过滤,并且为get型传参,观察到我们可以通过修改page后面的文件来达到读取其他文件的目的,找到一个被隐藏的file4
到这里我们已经可以利用这个漏洞去读取敏感文件phpinfo.php
medium
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\"" ), "", $file );
?>
可以看到这里对一些符号进行了过滤,我们有两种办法进行绕过
1.利用绝对路径,这里就利用了绝对路径进行绕过
2.利用双写绕过,....//....//
high
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
if( !fnmatch( "file*", $file ) && $file != "include.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}
?>
这里用了一个fnmatch函数,用来匹配page的参数是否为以file开头,不是则错误
这里我们可以用到网络通信中的file协议,以此来读取包含文件
impossible
<?php
// 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;
}
?>
这里的impossible难度的防护措施简单粗暴,直接限制你page参数只能是这四个,避免了文件包含漏洞,同时还可以使用路径限制禁止../跳转目录,或者白名单控制包含的文件或者尽可能避免像这样的动态包含来防护此漏洞
File Upload(文件上传)
文件上传漏洞是指用户通过界面上的上传功能上传了一个可执行的脚本文件,而WEB端的系统并未对其进行检测或者检测的逻辑做的不够好(通常都是上传头像等图片时)
通常来说文件上传功能是没有问题的,主要是服务器如何对上传的文件如何进行处理。
若WEB未对用户上传的文件进行有效的审查,若存在恶意用户对其上传一句话木马,从而实现控制WEB网站的目的。
low
<?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' ] );
// 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>";
}
}
?>
观察源码,发现只是简单的上传,并未有任何过滤,这里我们利用kali自带的获取shell工具weevely,首先生成一个木马
然后进行上传
显示上传成功,我们直接连接
接着我们便可以输入命令
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?
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>';
}
}
?>
这里多了文件类型和文件大小的限制我们上传时抓包修改一下 image/jpeg
再次成功上传
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?
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>';
}
}
?>
可以看到high难度的过滤是文件名中必须存在 .jpg .png等等
getimagesize这个函数同时读取了上传文件的文件头,必须是图片的文件头。我们选择将一个图片与木马合成进行上传
上传成功
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 ) . '-';
$target_file = md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
$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' ) {
$img = imagecreatefromjpeg( $uploaded_tmp );
imagejpeg( $img, $temp_file, 100);
}
else {
$img = imagecreatefrompng( $uploaded_tmp );
imagepng( $img, $temp_file, 9);
}
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();
?>
这里有两个关键函数
imagejpeg(image,filename,quality):函数销毁图像资源从image图像以filename为文件名创建一个JPEG图像,可选参数quality,范围从0(最差质量,文件更小)到100(最佳质量,文件最大)。
imagedestroy(img):函数销毁图像资源
相当于会将我们传进去的文件名会重新命名,并且原来的图像也会被重新构造一个,并不会解析其中的php代码以此起到了防护作用,并且用到了Anti-CSRFtoken防护csrf攻击,并且还未显示上传路径
Insecure CAPTCHA(不安全验证)
CAPTCHA是一种验证程序,主要是验证区分人和机器,就是防止有人恶意利用刷单爆破等操作,这里的不安全的其实就是指攻击者可以绕过验证来进行攻击
由于这里报错需要先把谷歌的验证码密钥添加进指定目录(不懂),而且本质上还是属于csrf,我们就直接根据源码看,不进行测试了
low
<?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=\"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 = 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();
}
?>
目测是一个修改密码,并且需要验证码的一个过程,大概就是step=1时服务器返回表单,对验证码进行验证是否正确,这里的recaptcha_check_answer函数就是进行了验证,正确则继续下一步,错误则返回错误信息不继续进行
recaptcha_check_answer($privkey,$remoteip, $challenge,$response)
step=2时会以post方式传入一系列参数给服务器,进行修改密码的过程,总体来看实际就是前面csrf换成post传参,这里的step为可控参数,可以bp抓包修改step为2来直接跳过对验证码的验证过程(这里这种分两步的验证方式谁会在现实里这样写啊........)
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();
}
?>
可以看到这里多了一段代码进行判断step为1是不是被跳过了,但是这里的pass_captcha参数也是可控的,跟low是一样的,修改参数跳过即可
// 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;
}
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' ],
$_SERVER[ 'REMOTE_ADDR' ],
$_POST[ 'recaptcha_challenge_field' ],
$_POST[ 'recaptcha_response_field' ] );
// Did the CAPTCHA fail?
if( !$resp->is_valid && ( $_POST[ 'recaptcha_response_field' ] != 'hidd3n_valu3' || $_SERVER[ 'HTTP_USER_AGENT' ] != 'reCAPTCHA' ) ) {
// 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 ) {
$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() . "' LIMIT 1;";
$result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' );
// Feedback for user
echo "<pre>Password Changed.</pre>";
}
else {
// Ops. Password mismatch
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
}
mysql_close();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
这里加了更多要验证的首先是recaptcha_response_field这个参数是否为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 = mysql_real_escape_string( $pass_new );
$pass_new = md5( $pass_new );
$pass_conf = $_POST[ 'password_conf' ];
$pass_conf = stripslashes( $pass_conf );
$pass_conf = mysql_real_escape_string( $pass_conf );
$pass_conf = md5( $pass_conf );
$pass_curr = $_POST[ 'password_current' ];
$pass_curr = stripslashes( $pass_curr );
$pass_curr = mysql_real_escape_string( $pass_curr );
$pass_curr = md5( $pass_curr );
// 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
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();
?>
这里的impossible排除用了Anti-CSRF token机制防止csrf以及pdo机制防止sql注入不说,这里的代码才像是正常人写的,没有分两步走,这样便会避免跳过验证,同时还加入了必须输入原密码才能修改密码的验证
SQL Injection(sql注入)
Sql 注入攻击是通过将恶意的 Sql 查询或添加语句插入到应用的输入参数中,再在后台 Sql 服务器上解析执行进行的攻击,它目前黑客对数据库进行攻击的最常用手段之一,这里我使用工具sqlmap进行测试
low
<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );
// Get results
$num = mysql_numrows( $result );
$i = 0;
while( $i < $num ) {
// Get values
$first = mysql_result( $result, $i, "first_name" );
$last = mysql_result( $result, $i, "last_name" );
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
// Increase loop count
$i++;
}
mysql_close();
}
?>
先输入一个1进去查看反应
发现还是有反应的,并且url中也有体现,直接输入命令
sqlmap -u " //测试的网址url// "
在sqlmap跑的过程中会有一些需要选择的地方,直接无脑选y回车就行
从这里可以看到存在注入点
开始注入
sqlmap -u " //测试的网址url// " --dbs
爆出数据库名
继续
sqlmap -u " //测试的网址url// " -D dvwa --tables
这时候没有爆出结果
按理是有结果的,尝试抓包发现里面同时还会传一个cookie值
在命令后加入cookie参数
sqlmap -u " //测试的网址url// " -cookie=" /抓到的cookie值/ " -D dvwa --tables
查询到两个表,我们要获得的信息应存储在users中,继续查询
sqlmap -u " //测试的网址url// " -cookie=" /抓到的cookie值/ " -D dvwa -T users --columns
得到
继续爆出用户名和密码
sqlmap -u " //测试的网址url// " -cookie=" /抓到的cookie值/ " -D dvwa -T users -C user,password --dump
注意遇到选择选y,直到遇到
这个选y会很慢,意思是 是否要使用通用密码后缀? 选n即可
得到结果
这时候对于我们来说这个网站的后台数据库已经被我们完全掌握,sqlmap会将查询到的内容直接显示出来
Midium
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = mysql_real_escape_string( $id );
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );
// Get results
$num = mysql_numrows( $result );
$i = 0;
while( $i < $num ) {
// Display values
$first = mysql_result( $result, $i, "first_name" );
$last = mysql_result( $result, $i, "last_name" );
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
// Increase loop count
$i++;
}
//mysql_close();
}
?>
观察url发现这个难度的环境是post方式传参并非get,通过抓包发现
于是我们要稍微修改刚才的命令
sqlmap -u " //测试的网址url// " -data="id=1&Submit=Submit" -cookie=" /抓到的cookie值/ " -D dvwa -T users -C user,password --dump
总体跟上面相同,加个data即可
High
<?php
if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
$id = $_SESSION[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysql_query( $query ) or die( '<pre>Something went wrong.</pre>' );
// Get results
$num = mysql_numrows( $result );
$i = 0;
while( $i < $num ) {
// Get values
$first = mysql_result( $result, $i, "first_name" );
$last = mysql_result( $result, $i, "last_name" );
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
// Increase loop count
$i++;
}
mysql_close();
}
?>
这个发现输入框会新出现一个对话框,同时也是post方式
这时候我们需要用到联合查询命令‘second-url’本质就是同时对两个页面注入
sqlmap -u " //新打开的提交数据页面url// " -data=" /抓到的返回值/ " --second-u=" /原本的显示数据页面url/ " -cookie=" /cookie值/ " -D dvwa -T users -C user,password --dump
总体跟上面大致相同,加入--second-u即可
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();
这里impossible难度同样用了Anti-CSRF token机制防止csrf以及pdo机制防止sql注入
SQL Injection (Blind)(盲注)
盲注:即在sql注入过程中,SQL语句执行查询后,查询数据不能回显到前端页面中,我们需要使用一些特殊的方式来判断或尝试,这个过程成为盲注
主要有布尔盲注,时间盲注等等,说白了就是去根据页面的响应去猜数据库的信息,一个一个去试去判断,由于其过程及其痛苦消耗脑力时间(不要问我怎么知道的),这里仍然不选择手注还是使用sqlmap注入😁😁(sqlmap yyds)
(步骤同上😀😀)
Reflected Cross Site Scripting (XSS)(反射型跨站脚本攻击)
xss是指恶意攻击者往web页面里插入恶意script代码,当用户浏览该页面时,嵌入其中web里面的script代码会被执行,从而达到恶意攻击用户的目的。xss漏洞通常是通过php的输出函数把javascript代码输出到html页面中,通过用户本地浏览器执行的,所以xss漏洞关键就是寻找参数未过滤的输出函数。
xss主要分为反射型,存储型,DOM型
low
这里看到有一个输入框,猜测存在反射型xss,观察源码
<?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>
这里array_key_exists函数判断name变量是否存在,并判断是否非空,若存在且非空,直接输出,这里就构成了xss漏洞
我们在输入框输入 <script>alert(document.cookie)</script>
medium
<?php
// 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>";
}
?>
这里str_replace函数把<script>替换来进行过滤,我们可以通过大小写,双写绕过此函数
输入<Script>alert(document.cookie)</script>
high
<?php
// 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>";
}
?>
这里的preg_replace正则匹配过滤了<script>标签,我们可以使用其他标签
<img src=0 οnerrοr=alert(document.cookie)>
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()函数把一些预定义的字符转换为 HTML 实体,防止其作为HTML元素作为xss攻击,并且 用了Anti-CSRF token防止csrf
Stored Cross Site Scripting (XSS)
存储型XSS又称之为持久型XSS,与反射型XSS,DOM型XSS相比,具有更高的隐蔽性。存储型XSS是最危险的一种跨站脚本,与前两者相比最大的区别在于反射型XSS和DOM型XSS执行都必须依靠用户手动去触发,存储型XSS却不需要。
low
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = mysql_real_escape_string( $message );
// Sanitize name input
$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();
}
?>
这里 trim() 函数是移除字符串两侧的空白字符或其他预定义字符,预定义字符包括、\t、\n、\x0B、\r以及空格等,stripslashes() 是删除字符串中的反斜杠。
可以看到这里的输入并未有任何过滤,我们直接输入<script>alert(document.cookie)</script>
由于是存储型,这里每刷新一次页面都会出现这个弹窗
medium
<?php
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的标签,addslashes() 函数返回在预定义字符(单引号、双引号、反斜杠、NULL)之前添加反斜杠的字符串
可以看到message参数的过滤规则十分严格,但是name却并不严格,我们可以尝试在参数name这里进行xss,但是name在前端似乎有个长度限制,那我们抓包,修改参数为<Script>alert(document.cookie)</script>
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 = mysql_real_escape_string( $message );
$message = htmlspecialchars( $message );
// Sanitize name input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $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();
}
?>
同样是name这里用了正则匹配来过滤,同样的抓包修改参数为
<img src=0 οnerrοr=alert(document.cookie)>
impossible
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = mysql_real_escape_string( $message );
$message = htmlspecialchars( $message );
// Sanitize name input
$name = stripslashes( $name );
$name = mysql_real_escape_string( $name );
$name = htmlspecialchars( $name );
// Update database
$data = $db->prepare( 'INSERT INTO guestbook ( comment, name ) VALUES ( :message, :name );' );
$data->bindParam( ':message', $message, PDO::PARAM_STR );
$data->bindParam( ':name', $name, PDO::PARAM_STR );
$data->execute();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
这里在所有的参数都用了 htmlspecialchars()函数过滤,防御xss