Cross-Site Request Forgery ,跨站请求伪造。通俗来讲,就是冒用用户身份进行攻击,有点像中间人攻击。它还有个亲戚,叫XSS,跨站脚本。
请求页面 比较直白,让我们修改 password
响应页面 难绷,不进行输入直接点 Change,居然直接修改了密码……
low http请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 GET /vulnerabilities/csrf/?password_new=&password_conf=&Change=Change HTTP/1.1 Host : localhostsec-ch-ua : sec-ch-ua-mobile : ?0sec-ch-ua-platform : ""Upgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.199 Safari/537.36Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Sec-Fetch-Site : same-originSec-Fetch-Mode : navigateSec-Fetch-User : ?1Sec-Fetch-Dest : documentReferer : http://localhost/vulnerabilities/csrf/Accept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9Cookie : PHPSESSID=re4k3en5q4a2e9pqidrd3it4a3; security=lowConnection : close
源码 仅仅是对两次输入进行了简单比较,然而没有丝毫过滤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?php if ( isset ( $_GET [ 'Change' ] ) ) { $pass_new = $_GET [ 'password_new' ]; $pass_conf = $_GET [ 'password_conf' ]; if ( $pass_new == $pass_conf ) { $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 ); $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>' ); echo "<pre>Password Changed.</pre>" ; } else { echo "<pre>Passwords did not match.</pre>" ; } ((is_null ($___mysqli_res = mysqli_close ($GLOBALS ["___mysqli_ston" ]))) ? false : $___mysqli_res ); }?>
攻击思路
将GET请求行URL伪装成短网址,诱导受害者点击。
这里我用 http://dwurl.cn/U/Index,将 http://localhost/vulnerabilities/csrf/?password_new=&password_conf=&Change=Change#生成短链 https://dwurl.cn/v7ef4Q
用 curl -i可以看到响应头,显示这个链接被重定向到 Location,直接修改密码为空。千言万语一句话,陌生链接不不要点,除非多方面求证。
配合XSS ,将修改密码的URL隐藏在html标签中,比如 <iframe> <script> <img>,操作会更加隐蔽
medium 源码 相比low 级别,striops()函数增加了 referer和 server_name的判断,检查了保留变量 HTTP_REFERER(http 包头的 Referer 参数的值,表示来源地址)中是否包含 SERVER_NAME(http 包头的 Host 参数,要访问的主机名),希望通过验证 http 来源的机制抵御 CSRF 攻击。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <?php if ( isset ( $_GET [ 'Change' ] ) ) { if ( stripos ( $_SERVER [ 'HTTP_REFERER' ] ,$_SERVER [ 'SERVER_NAME' ]) !== false ) { $pass_new = $_GET [ 'password_new' ]; $pass_conf = $_GET [ 'password_conf' ]; if ( $pass_new == $pass_conf ) { $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 ); $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>' ); echo "<pre>Password Changed.</pre>" ; } else { echo "<pre>Passwords did not match.</pre>" ; } } else { echo "<pre>That request didn't look correct.</pre>" ; } ((is_null ($___mysqli_res = mysqli_close ($GLOBALS ["___mysqli_ston" ]))) ? false : $___mysqli_res ); }?>
攻击思路 遇河搭桥。想办法实现referer字段绕过,实际情况还得配合其他漏洞组合攻击。
构造造一个exp.html,实现访问该exp.html就会自动修改密码。(怎么把exp.html放到服务器先不考虑)
绕过referer手段(三种均可)
目录混淆。将上述html放在127.0.0.1文件夹 下面http://localhost/127.0.0.1/exp.html
文件名混淆。将上述exp.html重命名为127.0.0.1.html http://localhost/127.0.0.1.html
URL混淆。http://localhost/exp.html?127.0.0.1
使受害者点击构造的html
high http请求 请求中间增加了重要参数 user_token,这个参数会成为接下来的老熟人。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 GET /vulnerabilities/csrf/?password_new=&password_conf=&Change=Change&user_token=2e509f6c9d959f644cca6f90b75f7f49 HTTP/1.1 Host : localhostsec-ch-ua : sec-ch-ua-mobile : ?0sec-ch-ua-platform : ""Upgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.199 Safari/537.36Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Sec-Fetch-Site : same-originSec-Fetch-Mode : navigateSec-Fetch-User : ?1Sec-Fetch-Dest : documentReferer : http://localhost/vulnerabilities/csrf/Accept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9Cookie : PHPSESSID=re4k3en5q4a2e9pqidrd3it4a3; security=highConnection : close
源码 如下,代码加入了 Anti-CSRF token 机制,当用户每次访问改密页面时,服务器会返回一个随机的 token。向服务器发起请求时,需要提交 token 参数,而服务器在收到请求时会检查 token,只有 token 正确时才会处理客户端的请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?php if ( isset ( $_GET [ 'Change' ] ) ) { checkToken ( $_REQUEST [ 'user_token' ], $_SESSION [ 'session_token' ], 'index.php' ); $pass_new = $_GET [ 'password_new' ]; $pass_conf = $_GET [ 'password_conf' ]; if ( $pass_new == $pass_conf ) { $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 ); $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>' ); echo "<pre>Password Changed.</pre>" ; } else { echo "<pre>Passwords did not match.</pre>" ; } ((is_null ($___mysqli_res = mysqli_close ($GLOBALS ["___mysqli_ston" ]))) ? false : $___mysqli_res ); }generateSessionToken ();?>
攻击思路 现在要想进行 CSRF 攻击就必须获取到用户的 token,而要想获取到 token 就必须利用用户的 cookie 去访问修改密码的页面,然后截取服务器返回的 token 值。 这里可以利用后面的XSS(Reflected) 的 high 级别的漏洞,,我们注入一个攻击脚本,使得每次打开页面时都弹出 token 值。 注入的 payload 如下。<iframe src="../csrf/" onload=alert(frames[0].document.getElementsByName('user_token')[0].value)>
impossible 学习时间到!
源码 这里相对于 High 级别主要就是增加了输入当前密码的选项,这个在实战中还是一种比较主流的防护方式,攻击者不知道原始密码的情况下是无法发起 CSRF 攻击的,另外常见的防护方法还有加验证码来防护。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <?php if ( isset ( $_GET [ 'Change' ] ) ) { checkToken ( $_REQUEST [ 'user_token' ], $_SESSION [ 'session_token' ], 'index.php' ); $pass_curr = $_GET [ 'password_current' ]; $pass_new = $_GET [ 'password_new' ]; $pass_conf = $_GET [ 'password_conf' ]; $pass_curr = stripslashes ( $pass_curr ); $pass_curr = ((isset ($GLOBALS ["___mysqli_ston" ]) && is_object ($GLOBALS ["___mysqli_ston" ])) ? mysqli_real_escape_string ($GLOBALS ["___mysqli_ston" ], $pass_curr ) : ((trigger_error ("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work." , E_USER_ERROR)) ? "" : "" )); $pass_curr = md5 ( $pass_curr ); $data = $db ->prepare ( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' ); $data ->bindParam ( ':user' , dvwaCurrentUser (), PDO::PARAM_STR ); $data ->bindParam ( ':password' , $pass_curr , PDO::PARAM_STR ); $data ->execute (); if ( ( $pass_new == $pass_conf ) && ( $data ->rowCount () == 1 ) ) { $pass_new = stripslashes ( $pass_new ); $pass_new = ((isset ($GLOBALS ["___mysqli_ston" ]) && is_object ($GLOBALS ["___mysqli_ston" ])) ? mysqli_real_escape_string ($GLOBALS ["___mysqli_ston" ], $pass_new ) : ((trigger_error ("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work." , E_USER_ERROR)) ? "" : "" )); $pass_new = md5 ( $pass_new ); $data = $db ->prepare ( 'UPDATE users SET password = (:password) WHERE user = (:user);' ); $data ->bindParam ( ':password' , $pass_new , PDO::PARAM_STR ); $data ->bindParam ( ':user' , dvwaCurrentUser (), PDO::PARAM_STR ); $data ->execute (); echo "<pre>Password Changed.</pre>" ; } else { echo "<pre>Passwords did not match or current password incorrect.</pre>" ; } }generateSessionToken ();?>
总结 CSRF 我们算是搞完了。攻击者目的就是在受害者不知情的情况下冒充受害者,执行恶意操作。防护方法主要有
设置请求头中Referer字段,确认请求来源页面。注意这个单词的拼写,它可有些故事。
设置Token,确认请求来自知道正在做什么的用户。有点绕,也就是确认是本人有意操作。
验证码,也是确认本人有意操作。