文章首发于先知社区php代码审计分享

0x01环境

搭建web服务器环境的软件有很多,使用最多的还是phpstudy以及宝塔面板,本文采用的是phpstudy。请注意,无论使用phpstudy还是宝塔面板,都需要按照网站源码的设计要求,修改环境配置,不然网站会搭建失败。

0x02思路

审计一个源码,最重要的是路由问题。一个功能点,如果找不到对应的功能代码,那审计的作用还有吗?

当然,没有使用框架的网站源码是没有路由效果的,直接通过url找目录路径文件就可以了

举个例子

一个网站的登录接口为 http://127.0.0.1/admin/login.php ,那么对应的源码就在admin目录下的login.php内

那如果使用了框架,情况就复杂起来了,需要了解这个源码使用框架的目录结构,thinkphp8.0框架的目录结构如下(源配置路径:https://doc.thinkphp.cn/v8_0/directory_structure.html),接口函数基本上在app下的controller文件夹下

www  WEB部署目录(或者子目录)
├─app 应用目录
│ ├─controller 控制器目录
│ ├─model 模型目录
│ ├─ ... 更多类库目录
│ │
│ ├─common.php 公共函数文件
│ └─event.php 事件定义文件

├─config 配置目录
│ ├─app.php 应用配置
│ ├─cache.php 缓存配置
│ ├─console.php 控制台配置
│ ├─cookie.php Cookie配置
│ ├─database.php 数据库配置
│ ├─filesystem.php 文件磁盘配置
│ ├─lang.php 多语言配置
│ ├─log.php 日志配置
│ ├─middleware.php 中间件配置
│ ├─route.php URL和路由配置
│ ├─session.php Session配置
│ ├─trace.php Trace配置
│ └─view.php 视图配置

├─view 视图目录
├─route 路由定义目录
│ ├─route.php 路由定义文件
│ └─ ...

├─public WEB目录(对外访问目录)
│ ├─index.php 入口文件
│ ├─router.php 快速测试文件
│ └─.htaccess 用于apache的重写

├─extend 扩展类库目录
├─runtime 应用的运行时目录(可写,可定制)
├─vendor Composer类库目录
├─.example.env 环境变量示例文件
├─composer.json composer 定义文件
├─LICENSE.txt 授权说明文件
├─README.md README 文件
├─think 命令行入口文件

举个接口例子

开源网站源码yylAdmin的管理员登录接口,我点击登录这个按钮,浏览器会向http://ming.com/admin/admin.Login/login路径下发送用户名以及密码

(ming.com是我phpstudy上设置的网站域名,会指向我的本地),但它对应的代码却在app/admin/controller/admin/Login.php文件里的 Login类的login函数

image-20231103114703442

image-20231103115212709

image-20231103115153247

如果按照没有框架的思路来看,接口url应该是/admin/controller/admin/login.php,完全不同

在这里

  • admin 表示控制器的命名空间或模块
  • admin.Login 表示控制器的类名
  • login 表示控制器的方法名

这便是thinkphp的命名规则

0x03审计(无框架)

审计源码为rapdicms(1.3.1版本)https://github.com/OpenRapid/rapidcms

安装

输入本地的数据库信息,设置密码,安装完成

image-20231103132703410

提示后台地址,那我们就在admin管理接口处搞起!

进来之后一个登录界面,打开burp,点击确认来锁定登录接口代码文件

image-20231103134707422

发现使用post请求向runlogin.php这个文件传递了两个参数,那么关键的代码文件就找到了

image-20231103134950644

image-20231103135102791

未授权

这个代码先使用include引用了variable.php的代码

使用file_get_contents读取了sql.json文件,然后连接了数据库,进而步入登录逻辑代码,sql.json蕴含着数据库的账号 密码信息,它使用file_get_contents读取,

那我们能不能直接访问获取到这个数据库信息呢,会不会有未授权漏洞呢?

直接访问http://ming.com/install/sql-config/sql.json泄露了数据库配置信息

image-20231103140219076

进入后台之后,跟随它的功能点去审计代码,进行白盒测试从而找出漏洞

image-20231103142535914

文件上传*2

基本设置处图标文件上传

image-20231103143102630

image-20231103143232304

image-20231103145758999

新增文章处文件上传

upload.php没有限制

image-20231103143731228

image-20231103143654521

sql注入*N

新增分类处insert注入

image-20231103145315331

image-20231103145223807

image-20231103145258972

查看分类处存在delete注入

image-20231103150110813

image-20231103150218760

查看分类处存在update注入

image-20231103150529134

image-20231103151045290

用户处有update注入

image-20231103151353839

image-20231103151524287

用户处有delete注入

image-20231103151619494

image-20231103151744490

新增文章处存在insert注入

image-20231103152037284

查看文章处存在update注入

image-20231103152232941

查看文章处存在delete注入

image-20231103152348333

普通用户评论文章处存在sql注入

image-20231103152907898

普通用户点赞评论处存在sql注入

image-20231103153045862

删除评论处存在sql注入

image-20231103153303437

登录逻辑绕过

让我们认真的分析以下这串登录代码,首先是通过执行sql语句获取了name的password值,赋给了$pa,然后进行判断,如果这串值和 POST请求接收的password值的md5加密、sha1加密、 md5加密后的值相同,那么setcookie

if ($link) {
$select = mysqli_select_db($link, $dataxxx['dbname']);
if ($select) {

$name = $_POST["username"];
$password = $_POST["password"];
if ($name == "" || $password == "") {
sendalert("请填写正确的信息");
exit;
}
//编写SQL语句并运行
$str = "select password from `rapidcmsuser` where username=" . "'" . "$name" . "'";
$str1 = "select yhxx from `rapidcmsuser` where username=" . "'" . "$name" . "'";
$result1 = mysqli_query($link, $str1);
$result = mysqli_query($link, $str);
$pass = mysqli_fetch_row($result);
$pass1 = mysqli_fetch_row($result1);
$pa = $pass[0];
$pas = $pass1[0];
$password11 = md5(sha1(md5($password)));
if ($pa == $password11) {

//设置Cookie,直接返回
setcookie("user", encode($name, $password11), time() + 3600000, '/');
setcookie("name", $name, time() + 3600000, '/');
sleep(2);
sendalert("登录成功");
} else {
sendalert("登录失败");
}
}
}

其实这里是由绕过方法的

使用联合注入带出注入的值,而这个值确是password经过md5、sha1、md5加密后的值

传入以下两个参数

username=admin1’ union select ‘021c6cd3a69730ac97d0b65576a9004f’ –

password=1

$str="select password from `rapidcmsadmin` Where username = 'admin'";

image-20231103141528355

image-20231103141646919

image-20231103153737316

直接绕过

修改密码处存在未授权访问

没有任何的cookie校验,可以直接修改密码

image-20231103154108624

image-20231103154505406

其实这里也有csrf,只不过影响相比于未授权差太少了

插件设置处存在任意删除

image-20231103155324481

id=../admin即可删除网站下的admin目录