0x00 写在前面
从零学习php,最终目的实现代码审计入门,软件采用sublime text,环境使用phpstudy搭建,数据库是navicat,需要有基本的前端基础、简单的php+mysql后端基础、渗透知识和漏洞原理,文章跟随流沙前辈学习记录,看看曾经遥不可及的代码审计能不能慢慢啃下来。
本章为代码审计入门第七篇-DVWA靶场篇,对DVWA靶场漏洞进行代码审计。
0x01 CSRF
CSRF模块,注释写在代码里,尽量用详细的语言描述代码含义。
level-low
- low.php
-
-
- if( isset( $_GET[ 'Change' ] ) ) {
- // Get input
- //设置并非空,GET型传参
- $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 );
- // 用mysqli_real_escape_string做一次简单过滤,给新密码进行一次加密
- // Update the database
- $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
- //更新数据库密码
- $result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '
'
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' ); -
- // Feedback for the user
- $html .= "
Password Changed.
"; - }
- else {
- // Issue with passwords matching
- $html .= "
Passwords did not match.
"; - }
-
- ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
- }
-
- ?>
更新数据库代码部分有一个函数dvwaCurrentUser,跟进看代码逻辑。

又发现一个 &dvwaSessionGrab,继续跟进

然后产生疑惑, &dvwaSessionGrab函数为什么要加& ,百度一番没看懂,吐槽一下:他喵的就不能用朴实无华的语言写出来,非得装杯。。。。。
好的,文章内容代码不是重点,重点是逻辑和漏洞。总结其方法的代码逻辑是判断用户是否登陆,如果用户已登陆把登陆的username返回来,然后修改其数据库对应的password。
为什么会产生漏洞?因为什么限制也没做。
level-medium
相比于low增加一个验证功能,作用简单讲就是检验referer和host值能否匹配上,也就是判断该头的主机地址是否来自DVWA,是继续不是拜拜。
- medium.php
-
-
- if( isset( $_GET[ 'Change' ] ) ) {
- // Checks to see where the request came from
- if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
- // Get input
- //stripos函数检测[ 'SERVER_NAME' ]在[ 'HTTP_REFERER' ]出没出现,出现下一步没出现false
- $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( '
'
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' ); -
- // Feedback for the user
- $html .= "
Password Changed.
"; - }
- else {
- // Issue with passwords matching
- $html .= "
Passwords did not match.
"; - }
- }
- else {
- // Didn't come from a trusted source
- $html .= "
That request didn't look correct.
"; - }
-
- ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
- }
-
- ?>
为什么有漏洞,因为burp抓包修改referer和host值就可以
level-high
相比于之前增加token认证,校验浏览器随机产生的token和用户提交修改密码时的token是否一致。其实感觉正常情况,基本有token机制就已经很难实现csrf了。
- high.php
-
-
- if( isset( $_GET[ 'Change' ] ) ) {
- // Check Anti-CSRF token
- checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
- // token验证
- // 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( '
'
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' ); -
- // Feedback for the user
- $html .= "
Password Changed.
"; - }
- else {
- // Issue with passwords matching
- $html .= "
Passwords did not match.
"; - }
-
- ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
- }
-
- // Generate Anti-CSRF token
- generateSessionToken();
-
- ?>

但是这里结合XSS还是存在风险的
漏洞组合拳之XSS+CSRF记录 - FreeBuf网络安全行业门户
level-impossible
既然是impossibale,那以咱水平肯定绕不过去,直接看代码。
代码做了PDO防注入,对于防csrf来讲,最大的区别在于代码要求用户输入原密码,不知道原密码也就不能进行csrf攻击了。
- impossible.php
-
-
- if( isset( $_GET[ 'Change' ] ) ) {
- // Check Anti-CSRF token
- checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
-
- // Get input
- $pass_curr = $_GET[ 'password_current' ];
- $pass_new = $_GET[ 'password_new' ];
- $pass_conf = $_GET[ 'password_conf' ];
-
- // Sanitise current password input
- $pass_curr = stripslashes( $pass_curr );
- $pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
- $pass_curr = md5( $pass_curr );
-
- // Check that the current password is correct
- $data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
- $data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
- $data->bindParam( ':password', $pass_curr, PDO::PARAM_STR );
- $data->execute();
-
- // Do both new passwords match and does the current password match the user?
- if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) {
- // It does!
- $pass_new = stripslashes( $pass_new );
- $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
- $pass_new = md5( $pass_new );
-
- // Update database with new password
- $data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' );
- $data->bindParam( ':password', $pass_new, PDO::PARAM_STR );
- $data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
- $data->execute();
-
- // Feedback for the user
- $html .= "
Password Changed.
"; - }
- else {
- // Issue with passwords matching
- $html .= "
Passwords did not match or current password incorrect.
"; - }
- }
-
- // Generate Anti-CSRF token
- generateSessionToken();
-
- ?>