Code-Breaking Puzzles
easy - function
1 | <?php |
代码很简单,如果匹配到字母数字下划线开头 就会显示代码,否则,就会把action当作函数处理,并且arg当作函数的一个参数,这里用到的是可变函数
可变函数:这意味着如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,并且尝试执行它。
第一点需要绕过/^[a-z0-9_]*$/
。这里可以用\绕过,因为在命名空间里\func()
表示的是调用全局空间函数func()
.如果直接写函数名fun()
调用,调用的时候其实相当于写了一个相对路径,而用\func()
,即使用的是绝对路径
例如有如下代码。
1 | <?php |
第二点就是$action('', $arg);
这里可变函数的使用,第一个参数是空 这里用到一个函数 create_function()
参考链接 http://blog.51cto.com/lovexm/1743442
具体用法:
create_function('','$GET_['fname']')
等价于
function f() { $GET_['fname']; }
于是传入参数为 1;}phpinfo();/*
即
function f(){1;}phpinfo();/*}
即可造成命令执行
于是构造payload
禁用了系统命令函数,只能去读flag,首先获取当前文件下的文件,找到flag文件名,用scandir()
函数,列出当前目录下文件,发现只有index.php文件,再找上级目录。http://51.158.75.42:8087/?action=\create_function&arg=2;}print_r(scandir(%27/var/www/%27));/*
发现flag文件,getflaghttp://51.158.75.42:8087/?action=\create_function&arg=2;}echo%20file_get_contents(%27/var/www/flag_h0w2execute_arb1trary_c0de%27);/*
easy - pcrewaf
1 | <?php |
首先waf函数is_php
过滤了被标签<? xxx;
所包裹的,即php文件
发现可以用<script>
标签绕过
但是从php7开始就不支持这种标签了,从之前的phpinfo来看题目环境也应该是7以上的版本
所以还是需要绕过这个正则,需要用到pcre的回溯限制问题
可以看到
1 | preg_match('/<\?.*[(`;?>].*/is', $data); |
在匹配后面这一部分 xxx;
的时候使用了非贪婪模式?
,PHP正则使用的pcre库属于NFA
- NFA:从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态
但是默认的回溯次数是有限制的
pcre.backtrack_limit //最大回溯数
默认为100万次,所以只要当回溯的次数大于100万次的时候,就会匹配失败。这里借用p师傅的一张图,就比如
1 | /<\?.*[(`;?>].*/is |
去匹配<?php phpinfo(); ?>//aaaa
可以看到先是.*
匹配了所有字符串,但是为了匹配
(;?>
就得回溯直到回溯到;
之前,就能匹配上[]
里的了,接着再去匹配下一个.*
于是
构造表单直接上传
上传后,直接跳转到生成的php下
然后再读flag
如何防御
用全等号来判断返回值,preg_match返回值为0或1
1 |
|
easy - phplimit
1 | <?php |
正则的意思 比如传入一个 phpinfo();
然后将;
之前的置为空,如果结果全等于;
,就执行eval
函数,前面/[^\W]+\((?R)?\)/
需要匹配到以数字,字母,下划线开头的函数,并且函数不能有参数,而这个(?R)
是正则里的递归匹配 能匹配asd(asd(ads()));
也就是说我们只能传进去这种嵌套的函数,而且还不能有参数
getflag_name
于是,获取flag名称的payloadprint_r(scandir(dirname(getcwd())));
getcwd()
:获取当前工作目录,本题就是/var/www/html
dirname()
获取上级目录,于是dirname(getcwd())
就是 /var/www
,
并且得用chdir()
函数切换到这个目录来读取这个目录下的文件
接着获取当前目录下的所有文件scandir()
但是返回的是一个数组 Array ( [0] => . [1] => .. [2] => flag_phpbyp4ss [3] => html )
所以得获得flag_phpbyp4ss
这个字符串
get_flag
http://51.158.75.42:8084/?code=print_r(file_get_contents(next(array_reverse(scandir(dirname(chdir(dirname(getcwd()))))))));
easy - nodechr
题目源码分析
题目给了源码,根据源码来看这题是一道koa框架的nodejs
main函数
1 | async function main() { |
从代码来看,main函数主要是创建连接了sqlite数据库users
表里插入了username,password,idflags
表中插入了id,flag
然后还写了路由
1 | router.all('login', '/login/', login).get('admin', '/', admin).get('static', '/static/:path(.+)', static).get('/source', source) |
safeKeyword方法
- i 不区分大小写匹配
- s 将字符串视为单行,换行符作为普通字符
1 | function safeKeyword(keyword) { |
对sql注入危险字符串进行了过滤
login函数查询代码
1 | //将用户名和密码转换为大写 |
解题思路
根据万能密码能够登陆,想到大概是让我们注入查询出flag
但是union select
又被过滤掉了
一开始想的是通过nodejs的函数readFileSync
但是有一个大写的转换,而nodejs函数是区分大小写的,于是行不通了
发现p神的文章
https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html
里面说到了toUpperCase
这个函数的问题
1 | "ı".toUpperCase() == 'I' |
于是,绕过了safeKeyword的正则,可进行union select注入
flag在flags表中
于是username填写(查字段数)
1 | admin' unıon ſelect flag 1,2,'3 |
第二位是回显位
于是构造
1 | 1' unıon ſelect 1,(ſelect flag from `flags`),'3 |
getflag
easy - phpmagic
代码分析
题目是用dig命令(查询域名)去执行我们传入的ip,看题目应该是一道命令执行
给出了源码,关键代码(分两)
1 | //...... |
大致是 我们POST的domain的结果会写入到$log_name这个文件中去
而这个文件默认是以
date('-Y-m-d')
做文件名
当我们传入POST[‘log’]就会以我们传入为文件名,也就是说文件名可控
之后又把文件名拼接为$_SERVER['SERVER_NAME'] . $log_name;
然后检测文件名的后缀,如果不在['php', 'php3', 'php4', 'php5', 'phtml', 'pht']
里面
就将dig的结果写入文件里
猜测利用点
初步猜测是绕过in_array 写入php文件
这里绕过php,猜测的方法有
- %00截断
- %0a截断
- /.绕过
后缀绕过
访问写入的文件
这里注意,目录结构http://51.158.75.42:8082/data/md5(ip)/host+$_POST['log']
这里我post的log是hu3sky.txt
dig的内容是127.0.0.1 | whoami
这样一来,就有思路了,于是我传入dig的内容<?php phpinfo() ;>
很尴尬,忘了代码中利用htmlspecialchars
进行实体编码了
不急,一步一步来,先绕过那个后缀判断
测试后,0a和/.都能绕过
即访问
1 | http://51.158.75.42:8082/data/a22cdc8b098b7ad0153672e2e3bf5edd/51.158.75.42hu3sky.php%0a |
文件写入
p师傅之前有一篇文章
https://www.leavesongs.com/PENETRATION/php-filter-magic.html
利用php://filter
base64的编码与解码写入文件
于是,可解码字符为
刚好4个一组,base64解码也是4个byte一组,所以不需要补
构造payload
注意修改Host,他会拼接
(Host不能包含: /等特殊字符)
1 | domain: base64后的<?=`ls`;/*** |
成功执行
final payloadPD89YGNhdCAnLi4vLi4vLi4vZmxhZ19waHBtYWcxY191cjEnYDsvKioq
lumenserial
初步判断
下载源码审计
禁用了函数
1 | system,shell_exec,passthru,exec,popen,proc_open,pcntl_exec,mail,apache_setenv,mb_send_mail,dl,set_time_limit,ignore_user_abort,symlink,link,error_log |
首先composer install
关键代码在app/Http/Controllers/EditorController.php
几个自认为比较关键的地方
download函数
1 | private function download($url) |
其中这个url函数是完全可控的
既然有file_get_contents
不难想到可以利用phar进行反序列化
寻找POP链
想要寻找phar的利用入口,就需要用到__destruct
,__call
函数对其他函数的调用
先全局搜索__destruct
,在vendor/illuminate/broadcasting/PendingBroadcast.php
1 | public function __destruct() |
跟进dispatch
方法
于是我们可以将$this->events
置为我们想要利用的类,而类没有dispatch
方法,从而去调用该类里的__call
方法
那么我们就要把目光投向__call
方法,__call
方法就很多了
ValidGenerator
在vendor/fzaninotto/faker/src/Faker/ValidGenerator.php
1 | public function __call($name, $arguments) |
$name
参数是要调用的方法名称。$arguments
参数是一个枚举数组,包含着要传递给方法 $name
的参数。
所以说这里的$name
就为dispatch
,不可控
在$res = call_user_func_array(array($this->generator, $name),
$arguments);
Generator
而这里$this->generator
为Generator
类
但是在Generator
类 由于找不到dispatch
方法,又会去调用Generator
类的__call
方法
1 | public function __call($method, $attributes) |
此时$method
是dispatch
不可控
format方法
1 | public function format($formatter, $arguments = array()) |
即$formatter
是dispatch
可控
getFormatter方法
1 | public function getFormatter($formatter) |
看到,这里return了一个fomatters数组,于是可以控制该数组
于是在format
的return call_user_func_array($this->getFormatter($formatter), $arguments);
这里$this->getFormatter($formatter)
就为数组,于是可以调用任意类的方法
于是让$formatters = array("$dispatch"=>"array($obj,"getFormatter")")
取出的$dispatch
就是 array($obj,"getFormatter")
然后 $obj=array('everything' => $evilobj)
这里需要两次调用Generator
类,嵌套调用
- 第一次:使
$dispatch
成为一个数组,让其值可控 - 第二次:返回任意类,使$res可控
(这个点饶了一万年。。。)
利用任意类
既然$res可控了,即call_user_func($this->validator, $res)
第二个参数可控
在vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnCallback.php
1 | private $callback; |
$this->callback
可控,getParameters
在接口
于是要找继承该接口的类
在vendor/phpunit/phpunit/src/Framework/MockObject/Invocation/StaticInvocation.php
1 | class StaticInvocation implements Invocation, SelfDescribing{ |
于是payload
1 | <?php |
将hu3sky.phar改名为gif 然后去上传图片,图片返回地址可以在F12里看到
然后,利用file_get_contents包含http://51.158.73.123:8080/server/editor?action=Catchimage&source[]=phar:///var/www/html/upload/image/e47ef897e343f580c9b59e4b9756074e/201901/23/dd9b81060394e6264c10.gif
就会生成马
学习与参考
https://www.cnblogs.com/iamstudy/articles/code_breaking_lumenserial_writeup.html
https://www.kingkk.com/2018/11/Code-Breaking-Puzzles-%E9%A2%98%E8%A7%A3-%E5%AD%A6%E4%B9%A0%E7%AF%87/#lumenserial