# 环境搭建:
# 1.DVWA 漏洞系统搭建
DVWA(Damn Vulnerable Web Application)
是一个用来进行安全脆弱性鉴定的 PHP/MySQL
Web
应用,旨在为安全专业人员测试自己的专业技能和工具提供合法的环境,帮助 web 开发者更好的理解 web 应用安全防范的过程
DVWA 共有十个模块,分别是:
- Brute Force(暴力(破解)
- Command Injection(命令行注入)
- CSRF(跨站请求伪造)
- File Inclusion(文件包含)
- File Upload(文件上传)
- Insecure CAPTCHA (不安全的验证码)
- SQL Injection(SQL 注入)
- SQL Injection(Blind)(SQL 盲注)
- XSS(Reflected)(反射型跨站脚本)
- XSS(Stored)(存储型跨站脚本)
同时每个模块的代码都有 4 种安全等级: Low
、 Medium
、 High
、 Impossible
。通过从低难度到高难度的测试并参考代码变化可帮助学习者更快的理解漏洞的原理
# 2.Windows 下搭建 DVWA
# 1. 下载并解压:
https://github.com/digininja/DVWA
# 2. 利用 phpstudy 搭建 web 服务器
# 3. 将 DVWA 放入本地站点目录(phpstudy)中
将解压出来的文件放入 D:\CTF\phpStudy_64\phpstudy_pro\WWW
目录下
【这里有个问题:以前的数据库和 phpstudy 的数据库都默认是 3306 端口,导致冲突无法启动】
# 3. 安装并配置 DVWA
# 1. 修改配置文件
在目录 D:\CTF\phpStudy_64\phpstudy_pro\WWW\DVWA-master\config
下将 config.inc.php.dist
重名命为 config.inc.php
然后将修改其内部代码:
$_DVWA = array(); | |
$_DVWA[ 'db_server' ] = '127.0.0.1'; | |
$_DVWA[ 'db_database' ] = 'dvwa'; | |
$_DVWA[ 'db_user' ] = 'root'; | |
$_DVWA[ 'db_password' ] = 'root'; | |
$_DVWA[ 'db_port'] = '3306'; |
# 2. 安装 DVWA
打开浏览器,输入 http://127.0.0.1/DVWA-master/index.php
进入,选择左侧的 Setup/Reset DB
选项,在最下方点击 Create/Reset Database
,如果提示 Setup successful!
即安装成功,然后会显示登录页面
# 3. 登录 DVWA
使用浏览器访问 http://127.0.0.1/DVWA-master/
,输入用户名 admin
,密码 password
即可登录
参考 1:https://cjjkkk.github.io/install-DVWA/
参考 2:https://www.jb51.net/article/198422.htm
# 1.Brute Force
# 1. 等级:Low
在 DVWA 文件里可找到源码:
<?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 | |
$html .= "<p>Welcome to the password protected area {$user}</p>"; | |
$html .= "<img src=\"{$avatar}\" />"; | |
} | |
else { | |
// Login failed | |
$html .= "<pre><br />Username and/or password incorrect.</pre>"; | |
} | |
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); | |
} | |
?> |
这里主要部分是:
$user = $_GET[ 'username' ]; | |
$pass = $_GET[ 'password' ]; | |
$pass = md5( $pass ); | |
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; | |
//result 为查询结果 | |
$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 方法传参,并没有对传入参数进行过滤,也没有其他的放爆破手段,所以这里我们可以进行爆破, burpsuite
里带有字典,可以尝试利用这个进行爆破
# burp 抓包
点击 Login,查看抓包结果:
然后进行 send to intruder
,在 intruder 页面下,清楚传入的变量:点击 clear$
,将 username 与 password 字段后面加入 add$
,设置 attack type
的值为 cluster bomb
在 payloads 下添加字典,payload1 这里手动添加几个用户名,payload2 也是手动加入几个密码进行验证,最后点击 start attack 即可开始暴力破解
破解结果可以看出来,其他的返回长度均为 4634,而只有一个长度为 4684,这个即为正确账户密码,其他都为失败时返回的内容,长度就不一样
利用这个登录,发现能够成功登录:
# 2. 等级: Medium
<?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); | |
} | |
?> |
这里注意到输入的用户名和密码被 mysqli_real_escape_string
函数过滤了一下,( mysqli_real_escape_string()
函数转义在 SQL 语句中使用的字符串中的特殊字符)
但是没有影响进行爆破(只不过在登录失败后 else 语句内有个 sleep(2)
会延缓我们的爆破时间)
仍然通过爆破:
登录即可
# 3. 等级: High
<?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(); | |
?> |
这里发现加了一行 checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
# checkToken 函数
主要用于防范 CSRF
攻击,其原理是通过比较用户提交的 token 值和后端存储的 token 值是否一致来判断请求是否合法,所以不能和上面一样进行直接的爆破
下面的图可以看到,新加入了 token
# 利用 burp 爆破:
爆破方式选择 Potchfork
:
进行 302 重定向:
设置 payload2 为递归搜索
Grep - Extract
添加一个 Grep
查询筛选, 接着点击获取返回包值,然后鼠标选择要提取的 token
运行:
提示不能进行多线程,所以改为单线程递归:
爆破结果:
# 2.SQL Injection
# 1. 等级: Low
<?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 | |
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; | |
} | |
mysqli_close($GLOBALS["___mysqli_ston"]); | |
break; | |
case SQLITE: | |
global $sqlite_db_connection; | |
#$sqlite_db_connection = new SQLite3($_DVWA['SQLITE_DB']); | |
#$sqlite_db_connection->enableExceptions(true); | |
$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 | |
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; | |
} | |
} else { | |
echo "Error in fetch ".$sqlite_db->lastErrorMsg(); | |
} | |
break; | |
} | |
} | |
?> |
发现:
$id = $_REQUEST[ 'id' ]; | |
.... | |
$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>' ); | |
while( $row = mysqli_fetch_assoc( $result ) ) { | |
$first = $row["first_name"]; | |
$last = $row["last_name"]; | |
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; | |
} |
发现没有进行过滤,利用万能密码 ' or 1=1#
试一下:
利用 ' union select user,password from users#
查询数据库其他内容
# 2. 等级:Medium
<?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 | |
$html .= "<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 | |
$html .= "<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"]); | |
?> |
这里加入了 mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
进行转义,并且 $id = $_POST[ 'id' ];
变成了 post 方式
这里直接利用 sqlmap 来进行自动注入
得到结果:
# 等级: high
<?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 | |
$html .= "<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 | |
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; | |
} | |
} else { | |
echo "Error in fetch ".$sqlite_db->lastErrorMsg(); | |
} | |
break; | |
} | |
} | |
?> |
这里是 $id = $_SESSION[ 'id' ];
利用了 SESSION 获得 id 值,因为 session 我们就不能在当前页面注入
防御了自动化的 SQL 注入,分析源码可以看到,对参数没有做防御,在 sql 查询语句中限制了查询条数
利用 1' union select user,password from users#
获取账户密码
# 3.SQL injection(Blind)
# 1. 等级: Low
<?php | |
if( isset( $_GET[ 'Submit' ] ) ) { | |
// Get input | |
$id = $_GET[ 'id' ]; | |
$exists = false; | |
switch ($_DVWA['SQLI_DB']) { | |
case MYSQL: | |
// Check database | |
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';"; | |
try { | |
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors | |
} catch (Exception $e) { | |
print "There was an error."; | |
exit; | |
} | |
$exists = false; | |
if ($result !== false) { | |
try { | |
$exists = (mysqli_num_rows( $result ) > 0); | |
} 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';"; | |
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 | |
$html .= '<pre>User ID exists in the database.</pre>'; | |
} else { | |
// User wasn't found, so the page wasn't! | |
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); | |
// Feedback for end user | |
$html .= '<pre>User ID is MISSING from the database.</pre>'; | |
} | |
} | |
?> |
这里的 id 是用 get 方法传入,没有进行过滤,但是输出值只有两种:
if ($exists) { | |
$html .= '<pre>User ID exists in the database.</pre>'; | |
} else { | |
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); | |
$html .= '<pre>User ID is MISSING from the database.</pre>'; | |
} |
这里是布尔型盲注
盲注常用函数:
length() 返回字符串的长度,例如可以返回数据库名字的长度
substr() ⽤来截取字符串 substr(str,start,stop)
substr截取字符串str,从start开始截取,截取stop个字符
ascii() 返回字符的ascii码
sleep(n) 将程序挂起⼀段时间,n为n秒
if(expr1,expr2,expr3) 判断语句 如果第⼀个语句正确就执⾏第⼆个语句如果错误执⾏第三个语句
使用 1' and 1=1#
存在
输入 1' and 1=2#
不存在
判断是字符型注入
利用 length()
来判断其数据库名的长度:
1' and length(database())=1#
错误
1' and length(database())=4#
一直尝试到 4 才成功,证明数据库名长度为 4
通过 ascii()
与 substr()
来尝试得到数据库名称,需要利用二分法来猜测
1' and ascii(substr(database(),1,1))<97#
返回错误,所以肯定大于 a
1' and ascii(substr(database(),1,1))<123#
存在
重复这种方式查找即可,最后得到数据库名称是 DVWA
利用 count函数
来判断 table_name
数量有几个
1' and (select count(table_name) from information_schema.tables where table_schema=database())=1 #
为 2 时返回正常:
1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=1 #
查询表的长度
为 9 时返回正确:
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=97 #
猜测表的名字
第一个为 g
最后得到两个表名分别为 guestbook
和 users
查询表的字段名:
1' and ascii(substr((select column_name from information_schema.columns where table_name= 'users' limit i,1),n,1))=101 #
注释:
i代表查询第几个字段名
n代码查询字段名的第几个字符
得到第四个字段和第五个字段分别为 user
和 password
查询字段内的数据:
1' and ascii(substr((select user from dvwa.users limit 0,1),1,1))=97 #
最后得到为 admin,然后可以通过这种方式得到 password 和其他数据
# 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;"; | |
try { | |
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors | |
} catch (Exception $e) { | |
print "There was an error."; | |
exit; | |
} | |
$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 | |
$html .= '<pre>User ID exists in the database.</pre>'; | |
} else { | |
// Feedback for end user | |
$html .= '<pre>User ID is MISSING from the database.</pre>'; | |
} | |
} | |
?> |
可以看到这里又使用了 mysqli_real_escape_string函数
,对特殊字符进行转义
也限制了我们的输入,使用 burp 抓包来修改(这里是 post 方式):
可以在这里修改输入的 id,进行 sql 盲注
# 3. 等级: high
<?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;"; | |
try { | |
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors | |
} catch (Exception $e) { | |
$result = false; | |
} | |
$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 | |
$html .= '<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 | |
$html .= '<pre>User ID is MISSING from the database.</pre>'; | |
} | |
} | |
?> |
这里是利用 cookie 方式传递,并且有个 sleep(rand(2,4))
来干扰时间盲注,这里仍然是利用布尔型盲注
1' and length(database())=4#
得到数据库名长度
后续方式和等级 low
类似
# 4.XSS(Reflected)
# 1. 等级: low
<?php | |
header ("X-XSS-Protection: 0"); | |
// Is there any input? | |
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) { | |
// Feedback for end user | |
$html .= '<pre>Hello ' . $_GET[ 'name' ] . '</pre>'; | |
} | |
?> |
源码通过 get 方式获得 name,没有进行过滤,输入 js 脚本即可执行
<script>alert(123)</script>
# 2. 等级: Medium
<?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
$html .= "<pre>Hello {$name}</pre>";
}
?>
这里 str_replace( '<script>', '', $_GET[ 'name' ] );
进行了过滤,将 <script>
过滤掉了,不过没有限制大小写,这里直接大写 <Script>
就能绕过
<Script>alert(321)</script>
# 3. 等级: 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 | |
$html .= "<pre>Hello {$name}</pre>"; | |
} | |
?> |
这里 preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] )
利用了正则过滤,防止了 <script>
,这里可以利用 img,body 标签的 src 来输入 js 代码
<img src=1 onerror=alert(111)>
# 5.XSS(Stored)
<?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(); | |
} | |
?> |
对输入的信息进行了过滤但是,没有进行 XSS 的过滤
trim(string,charlist) : 移除string字符两侧的预定义字符
stripslashes(string): 去除掉string字符的反斜杠\
mysqli_real_escape_string(string,connection):对特殊字符进行转义
输入 cyb
和 <script>alert(123)</script>
# 2. 等级: Medium
<?php | |
if( isset( $_POST[ 'btnSign' ] ) ) { | |
// Get input | |
$message = trim( $_POST[ 'mtxMessage' ] ); | |
$name = trim( $_POST[ 'txtName' ] ); | |
// Sanitize message input | |
$message = strip_tags( addslashes( $message ) ); | |
$message = ((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(); | |
} | |
?> |
这里对名字进行了 <script>
过滤: $name = str_replace( '<script>', '', $name );
,对 massage 进行了 htmlspecialchars( $message );
将预定义字符转为 HTML 实体
在前端写被限定了 name 的输入长度,所以就利用 burp 抓包来改写:
修改利用大写 <Script>
绕过检测:
修改为 <Script>alert(321)</script>
# 3. 等级: 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(); | |
} | |
?> |
这里对 name 进行了正则过滤,通过抓包的方式来利用 img
标签执行代码
抓包修改为 <img src=1 onerror=alert(555) />
,也就是:
<img%20src=1%20onerror=alert(555)%20/>
修改:
成功执行: