代码审计总结

概述

代码审计是企业安全运营以及安全从业者必备的安全基础能力。代码审计在很多时候都需要用到,比如渗透测试、漏洞研究、安全运营等等,它以开放的形式从代码层面寻找漏洞、验证漏洞、修复漏洞。

源代码审计工作利用一定的编程规范和标准,针对应用程序源代码,从结构、脆弱性以及缺陷等方面进行审查,来发现当前应用程序中存在的安全缺陷以及代码的规范性缺陷。

工具

  1. 环境搭建(phpstudy)
  2. 自动化审计工具
  3. IDE
  4. 测试工具(burpsuite、firebug等)
  5. 代码编辑器(Sublime、Notepad++、UltraEdit等)

自动化审计工具的分析结果:
tool

事前工作

  1. 了解Web应用所采用的编程语言的版本漏洞、语言特性、运行机制。
  2. 了解Web应用所采用的框架的结构以及运行流程。
  3. 了解Web应用常规的安全漏洞、代码样本以及修复方案。
  4. 了解Web应用的类型、编码方式等。

思路

在团队工作中,采用标准化的编码方式、注释尤为重要,这样可以提高代码的可读性,降低后期修改代码的难度。对企业应用、CMS进行代码审计也可以充分利用这一点。

在代码审计之前先查看整个代码的目录结构,例如文件的命名、修改时间、大小等。因为根据这些文件的命名我们可以大概知道这个程序实现了哪些功能、核心文件是什么等等。

fold

从上图基本可以看出config目录是配置文件、controllers是控制器、helper是辅助函数、models是模型、view是视图、third_party是第三方组件、language肯定和语言有关等等。

然后再根据通用的思路来进行审计:

查看Web应用所采用的框架、第三方库是否存在安全漏洞。

对于Web应用而言,大多是都是采用网上现成的成熟的框架进行开发的,也会经常会调用第三方组件。这个时候我们就要关注它所采用的框架、第三方组件是否存在安全漏洞。

比如Struts、Spring、ThinkPHP、Django等框架的漏洞以及Apache Common Collection等第三方组件的漏洞。

这些可以根据框架/第三方组件的名称、版本来进行查询。

查看公共函数、拦截函数是否存在安全漏洞或者可以绕过的风险。

一般情况下,公共函数、拦截函数在文件、函数命名上会包含commonfunctionfiltersafecheck等关键词,然后提供给其他文件统一调用。可以查看此类函数的安全机制是否存在绕过的可能性:

Java可查看全局文件web.xml

下面是一个过滤器的函数代码:

1
2
3
4
5
6
7
8
9
10
// Java
public class RequestEncodingFilter implements Filter {
...
try{
invalidInputPattern = myComplier.complie("<[\\s\\x00]*SCRIPT|SELECT\\s|INSERT\\s|DELETE\\s|UPDATE\\s|DROP\\s|<!--|-->|<FRAME|<IFRAME|<FRAMESET|<NOFRAME|<PLAINTEXT|<A\\s|<LINK|<MAP|<BGSOUND|<IMG|<FORM|<INPUT|<SELECT|<OPTION|<TEXTAREA|<APPLET|<OBJECT|<EMBED|<NOSCRIPT|<STYLE|ALERT[\\s\\x00]*\\(|<|>|\"");
} catch(Exception err){
invalidInputPattern = null;
}
...
}

可以看到,如果存在SQL注入漏洞,可用/**/代替空格的方式绕过,例如:SELECT/**/id/**/From/**/table的方式绕过,另外对于标签内利用on事件进行XSS 也没有拦截作用,例如:

1
’ onclick=confirm`1` xx=’

查看敏感函数,回溯变量,判断变量是否在调用前进行过严格的安全过滤。

根据敏感函数来逆向追踪参数的传递过程,这是目前来说最为常用的一种方式。因为大多是的漏洞都是因为函数的使用不当或者在函数使用前安全处理不够所造成的。

1
2
3
4
5
// PHP
......
$str = @(string)$_GET['str'];
eval('$str="'.addslashes($str).'";');
......

虽然这里用addslashes()函数进行了过滤,但是提交的php代码可以这样在双引号中被执行:

phpinfo

当然还有一些比如SQL注入等问题可以直接通过搜索selectinsert等关键词来进行审计的:

1
2
3
4
5
6
// Java
......
Statement statement = con.createStatement();
String sql = "select * from users where id='" + id + "'";
ResultSet rs = statement.executeQuery(sql);
......

string

找到关键点后然后一步一步逆向查看关键变量是否可控。

查看敏感功能点,正向追踪变量传递过程。

有了一定的渗透、代码审计经验之后,就会知道哪些功能点会存在哪些问题。

例如:PHPCMS任意前台用户密码重置漏洞

基础知识

Web框架

Web 应用框架,是一种开发框架,主要用来支持动态网站、网络应用程序以及网络服务的开发。

常见的框架如下所示:

Java: Struts、Spring
PHP: ThinkPHP、CodeIgniter、Yii
Python: Django、Tornado

Web框架模式:

MVC是最常见的开发架构,它强制性的使应用程序的输入、处理和输出分开。使用MVC应用程序被分成三个核心部件:模型、视图、控制器。它们各自处理自己的任务,最典型的MVC就是JSP + servlet + javabean的模式。

MVC

框架的安全特质:

  1. 拦截器
  2. CSRF防护
  3. SQL注入防护
  4. XSS防护

虽然大部分框架都具有一些安全特质,但是毕竟代码还是人写的,开发者如果对框架以及安全了解不足,还是会用自己的编码习惯而不是采用框架提供的安全函数去编写Web应用,那么常规的漏洞还是可能会出现的。

还有一些Web应用可能是自己团队开发的框架,但是八九不离十,还是按照前面提到的思路来。

编码

这里特地要提一下统一编码的重要性,前后端的编码不一致会引发诸多的安全问题(php与mysql交互过程中发生的编码转换导致SQL宽字节注入等)。

用户可控变量

基于一切输入都是不可信任的原则,要对所有的外来输入都要进行安全过滤、验证。

用户可控变量包括用户所在的客户端能够提交的变量:例如GET、POST、COOKIE、Referer、Content-Type、User-Agent、Host以及X-FORWARDED-FOR、Proxy等等。

1
2
3
4
5
6
7
8
9
10
11
POST /storage/1.5/499041/storage/tabs?batch=true&commit=true HTTP/1.1
Host: xxx.xxx.com
User-Agent: Firefox/54.0.1 (Windows NT 10.0; WOW64) FxSync/1.56.0.20170628075643.desktop
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Content-Type: text/plain
Connection: close
Pragma: no-cache
Cache-Control: no-cache
[{"payload":"{\"ciphertext\":\"JLIhhuoBa4AkbFGxBTSfltZD+xy95wdQzq5nPPxZqWs4bpuYxmG1tmX8GXxd3eGS8U3cf6qilhRwqbnxGyfTwFXLnW8uVjzaHpnijtkIkS6zJltyKCDVUKz/JHiHHcOAJ0SVT2VXsJBjl2LiCDTR+A==\",\"IV\":\"nOnwHS0aT7EPa+6CQip9HQ==\",\"hmac\":\"33f54d9e80834afecd1e05966c9aafb27ab72b2c8a80e87769bb328f43446229\"}","id":"5FXzEHRBZX2y","ttl":1814400}]

正因为用户可以控制JS,所以所有的安全过滤均要在服务器端完成。

PHP

配置文件

对于PHP,在代码审计之前需要了解PHP官方配置说明

这些模式决定着一个 PHP 的指令在何时何地,是否能够被设定。手册中的每个指令都有其所属的模式。例如有些指令可以在 PHP 脚本中用 ini_set() 来设定,而有些则只能在 php.ini 或 httpd.conf 中。

例如 output_buffering 指令是属于 PHP_INI_PERDIR,因而就不能用 ini_set() 来设定。但是 display_errors 指令是属于 PHP_INI_ALL 因而就可以在任何地方被设定,包括 ini_set()。

PHPINI* 模式的定义:

模式 含义
PHP_INI_USER 可在用户脚本(例如 ini_set())或Windows注册表(自PHP5.3起)以及.user.ini中设定
PHP_INI_PERDIR 可在 php.ini,.htaccess 或 httpd.conf 中设定
PHP_INI_SYSTEM 可在 php.ini 或 httpd.conf 中设定
PHP_INI_ALL 可在任何地方设定

具体可以根据首页文件index.php ,了解程序运作时调用哪些函数和文件 以index.php文件作为标线,一层一层去扩展阅读所包含的文件,了解其功能,之后进入其功能文件夹的首页文件,进扩展阅读。

PHP版本姿势

官方版本变更说明

php5.2以前:

  1. __autoload加载类文件,但只能调用一次这个函数,所以可以用spl_autoload_register加载类

php5.3:

  1. 新增了glob://和phar://流包装
    glob用来列目录,绕过open_baedir
    phar在文件包含中可以用来绕过一些后缀的限制
  2. 新的全局变量DIR
  3. 默认开启<?= $xxoo;?>, 5.4也可用

php5.4:

  1. 移除安全模式、魔术引号
  2. register_globals 和 register_long_arrays php.ini 指令被移除。
  3. php.ini新增session.upload_progress.enabled,默认为1,可用来文件包含

php5.5:

  1. 废除preg_replace的/e模式(不是移除)
    当使用被弃用的 e 修饰符时, 这个函数会转义一些字符(即:’、”、 \ 和 NULL) 然后进行后向引用替换。

php5.6:

  1. 使用 运算符定义变长参数函数

php7.0:

  1. 十六进制字符串不再是认为是数字
  2. 移除asp和script php标签
    1
    <% %><%= %><script language="php"></script>

php7.1:
1、废除mb_ereg_replace()mb_eregi_replace()的Eval选项

PHP 其他注意点

  1. php可以解析的文件后缀:php php4 php5 php6 phtml pht phps(部分要配置过)
  2. 当.或者[]之类的符号作为参数的key值时,会被php改写为_符号,但是QUERY_STRING为用户提交的内容,所以不能修改
  3. PHP的魔术方法
  4. …..

变量

用户可以直接控制的变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$_GET:http://localhost/mm.php?a=xxxxx
$_POST:
$_COOKIE:
记录在我们本地浏览器中的变量,是可控的。PHP中还有一个变量$_SESSION。
每个人访问网站,他的phpsessid都是不一样的,这个值就用来区分每个用户。服务器用PHPSESSID=cmebf7jkflu5a31vf67kbiopk4来标示每个用户,是否登录或者是否是管理员。
$_FILES:
可能产生的漏洞类型:
01.上传漏洞,上传一个php木马,相当于直接getshell了
02.注入,有些cms会把name的值保存在数据库里,但又没有对name进行过滤。
$_SERVER:其中部分我们可以控制。
X-FORWARDED-FOR:IP地址,很多cms取ip是首先取这个变量中的值,如果没有这个变量,才去取我们的真实Ip.
Referer:来源地址,我们访问目标页面的来源
Host:目标网址这几个变量就是我们php中间用户可以控制的变量。
大部分的漏洞都是 从这几个变量开始展开的。
$_REQUEST 就是$GET/$_POST/$COOKIE

Java

一般的web工程中都会用到web.xml,web.xml主要用来配置,可以方便的开发web工程。web.xml主要用来配置Filter、Listener、Servlet等。但是要说明的是web.xml并不是必须的,一个web工程可以没有web.xml文件。

经过个人测试,WEB工程加载顺序与元素节点在文件中的配置顺序无关。即不会因为 filter 写在 listener 的前面而会先加载 filter。WEB容器的加载顺序是:ServletContext -> context-param -> listener -> filter -> servlet。并且这些元素可以配置在文件中的任意位置。

加载过程顺序如下:

  1. 启动一个WEB项目的时候,WEB容器会去读取它的配置文件web.xml,读取listener><context-param>两个结点。
  2. 紧急着,容创建一个ServletContext(servlet上下文),这个web项目的所有部分都将共享这个上下文。
  3. 容器将<context-param>转换为键值对,并交给servletContext。
  4. 容器创建<listener>中的类实例,创建监听器。

<context-param>上下文参数

1
2
3
4
5
<context-param>
<param-name>ContextParameter</para-name>
<param-value>test</param-value>
<description>It is a test parameter.</description>
</context-param>

<filter>过滤器

将一个名字与一个实现javaxs.servlet.Filter接口的类相关联。

1
2
3
4
5
6
7
8
9
10
11
12
<filter>
<filter-name>setCharacterEncoding</filter-name>
<filter-class>com.myTest.setCharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>setCharacterEncoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<listener>监听器

1
2
3
<listener>
<listerner-class>com.listener.SessionListener</listener-class>
</listener>

<servlet>

  1. <servlet></servlet> 用来声明一个servlet的数据,主要有以下子元素:
  2. <servlet-name></servlet-name> 指定servlet的名称
  3. <servlet-class></servlet-class> 指定servlet的类名称
  4. <jsp-file></jsp-file> 指定web站台中的某个JSP网页的完整路径
  5. <init-param></init-param> 用来定义参数,可有多个init-param。在servlet类中通过getInitParamenter(String name)方法访问初始化参数
  6. <load-on-startup></load-on-startup>指定当Web应用启动时,装载Servlet的次序。当值为正数或零时:Servlet容器先加载数值小的servlet,再依次加载其他数值大的servlet 当值为负或未定义:Servlet容器将在Web客户首次访问这个servlet时加载它。
  7. <servlet-mapping></servlet-mapping> 用来定义servlet所对应的URL,包含两个子元素
  8. <servlet-name></servlet-name> 指定servlet的名称
  9. <url-pattern></url-pattern> 指定servlet所对应的URL
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
<!-- 基本配置 -->
<servlet>
<servlet-name>snoop</servlet-name>
<servlet-class>SnoopServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>snoop</servlet-name>
<url-pattern>/snoop</url-pattern>
</servlet-mapping>
<!-- 高级配置 -->
<servlet>
<servlet-name>snoop</servlet-name>
<servlet-class>SnoopServlet</servlet-class>
<init-param>
<param-name>foo</param-name>
<param-value>bar</param-value>
</init-param>
<run-as>
<description>Security role for anonymous access</description>
<role-name>tomcat</role-name>
</run-as>
</servlet>
<servlet-mapping>
<servlet-name>snoop</servlet-name>
<url-pattern>/snoop</url-pattern>
</servlet-mapping>

设置jsp

<jsp-config> 包括 <taglib><jsp-property-group> 两个子元素。其中<taglib>元素在JSP 1.2 时就已经存在;而` 是JSP 2.0 新增的元素。

`元素主要有八个子元素,它们分别为:

  1. <description>:设定的说明
  2. <display-name>:设定名称
  3. <url-pattern>:设定值所影响的范围,如: /CH2 或 /*.jsp
  4. <el-ignored>:若为 true,表示不支持 EL 语法
  5. <scripting-invalid>:若为 true,表示不支持 <% scripting %>语法
  6. <page-encoding>:设定 JSP 网页的编码
  7. <include-prelude>:设置 JSP 网页的抬头,扩展名为 .jspf
  8. <include-coda>:设置 JSP 网页的结尾,扩展名为 .jspf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<jsp-config>
<taglib>
<taglib-uri>Taglib</taglib-uri>
<taglib-location>/WEB-INF/tlds/MyTaglib.tld</taglib-location>
</taglib>
<jsp-property-group>
<description>Special property group for JSP Configuration JSP example.</description>
<display-name>JSPConfiguration</display-name>
<url-pattern>/jsp/* </url-pattern>
<el-ignored>true</el-ignored>
<page-encoding>GB2312</page-encoding>
<scripting-invalid>true</scripting-invalid>
<include-prelude>/include/prelude.jspf</include-prelude>
<include-coda>/include/coda.jspf</include-coda>
</jsp-property-group>
</jsp-config>

Python

Python 的Web框架我目前只接触了django,Django参照于MVC模式,但又不完全相同。它一般被称为MTV模式(Model数据存取层、Template表现层、View业务逻辑层)。它的路由一般都在urls.py中配置,框架其他情况可以去网上搜索其教程。

漏洞

文件操作漏洞

文件上传

文件上传过程中,通常因为未校验上传文件后缀类型,或者中间件解析问题,导致用户可上传php等一些webshell文件。代码审计时可重点关注对上传文件类型是否有足够安全的校验,以及是否限制文件大小等。

应用场景:图片上传、附件上传等。

关键词/接口/类包:

1
2
3
4
5
6
7
// Java
MultipartFile
...
// PHP
move_uploaded_file()
getimagesize()

修复方案:

  1. 使用白名单校验上传文件类型、大小限制。
  2. 对上传文件名(包括后缀)进行重命名。
  3. 将文件统一存放至文件服务器。

文件读取

关键词/接口/类包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// PHP
hightlight_file($filename);
show_source($filename);
print_r(php_strip_whitespace($filename));
print_r(file_get_contents($filename));
readfile($filename);
print_r(file($filename)); // var_dump
fread(fopen(filename,"r"),filename,"r"),size);
include($filename); // 非php代码
include_once($filename); // 非php代码
require($filename); // 非php代码
require_once($filename); // 非php代码
print_r(fread(popen("cat flag", "r"), $size));
print_r(fgets(fopen($filename, "r"))); // 读取一行
fpassthru(fopen($filename, "r")); // 从当前位置一直读取到 EOF
print_r(fgetcsv(fopen(filename,"r"),filename,"r"),size));
print_r(fgetss(fopen($filename, "r"))); // 从文件指针中读取一行并过滤掉 HTML 标记
print_r(fscanf(fopen("flag", "r"),"%s"));
print_r(parse_ini_file($filename)); // 失败时返回 false , 成功返回配置数组

文件包含

php支持的协议可以查阅官方手册

漏洞代码示例-1:

1
2
3
4
5
6
// PHP
12345
<?php
include($_GET['file'].'.php');
//?file=2.txt%00 【PHP版本小于5.3】

漏洞代码示例-2:

1
2
3
4
5
6
// PHP
12345
<?php
include($_GET['file']);
//?file=php://filter/convert.base64.encode(内容被base64编码)/resource=example.txt

关键词/接口/类包:

1
2
3
4
5
6
7
8
9
10
// PHP
fopen()
file_get_contents()
curl_exec()
readfile()
require()
require_once()
include()
include_once()
allow_url_include = on

文件删除

关键词/接口/类包:

1
2
3
// PHP
unlink()
session_destroy()

目录列举

关键词/接口/类包:

1
2
3
4
5
6
// PHP
print_r(glob("*")); // 列当前目录
print_r(glob("/*")); // 列根目录 print_r(scandir("."));
print_r(scandir("/"));
$d=opendir(".");while(false!==($f=readdir($d))){echo"$f\n";}
$d=dir(".");while(false!==($f=$d->read())){echo$f."\n";}

代码/命令执行

代码执行函数

应用场景:需要自定义执行系统代码的地方。

漏洞代码实实例-1:

1
2
3
// Python
import cPickle
cPickle.loads("cos\nsystem\n(S'uname -a'\ntR.")

漏洞代码实实例-2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Python
@login_required
@permission_required("accounts.newTask_assess")
def targetLogin(request):
req = simplejson.loads(request.POST['loginarray'])
req=unicode(req).encode("utf-8")
loginarray=eval(req)
p=_e(request,'ipList')
#targets=base64.b64decode(targets)
(iplist1,iplist2)=getIPTwoList(ip)
iplist1=list(set(iplist1))
iplist2=list(set(iplist2))
loginlist=[]
delobjs=[]
holdobjs=[]

关键词/接口/类包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Java
...
// PHP
eval()
assert()
preg_replace + '/e'
call_user_func()
call_user_func_array()
create_function
array_map()
...
// Python
eval
pickle.loads

修复方案:

  1. 避免命令用户可控
  2. 如需用户输入参数,则对用户输入做严格校验,如&&、|、;等

命令执行漏洞

由于业务需求,程序有可能要执行系统命令的功能,但如果执行的命令用户可控,业务上有没有做好限制,就可能出现命令执行漏洞。

应用场景:需要执行系统命令的地方。

漏洞代码示例-1:

1
2
3
4
5
6
7
// Python
def myserve(request, filename, dirname):
re = serve(request=request,path=filename,document_root=dirname,show_indexes=True)
filestr='authExport.dat'
re['Content-Disposition'] = 'attachment; filename="' + urlquote(filestr) +'"'fullname=os.path.join(dirname,filename)
os.system('sudo rm -f %s'%fullname)
return re

关键词/接口/类包:

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
// Java
Runtime.exec
ProcessBuilder.start
GroovyShell.evaluate
...
// PHP
system()
passthru()
exec()
pcntl_exec()
shell_exec()
popen()
proc_open()
`(反单引号)
ob_start()
escapeshellcmd() // 该函数用于过滤
....
// Python
os.system
os.popen
commands.getoutput
commands.getstatusoutput
subprocess

修复方案:

  1. 避免命令用户可控
  2. 如需用户输入参数,则对用户输入做严格校验,如&&、|、;等

序列化与反序列化

在现有很多的应用当中,需要对某些对象进行序列化,让它们离开内存空间,入驻物理硬盘,以便可以长期保存,其中最常见的是Web服务器中的Session对象。对象的序列化一般有两种用途:把对象的字节序列永久地保存到硬盘上,通常存放在一个指定文件中;或者在网络上传送对象的字节序列。

而把字节序列恢复为对象的过程称为对象的反序列化。当两个进程在进行远程通信时,彼此可以发送各种类型的数据,而且无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

当应用代码从用户接受序列化数据,并试图反序列化改数据进行下一步处理时,会产生反序列化漏洞,其中最有危害性的就是远程代码注入。

这种漏洞产生原因是,执行反序列化时,并不会对自身的输入进行检查,这就说明恶意攻击者可能也可以构建特定的输入,在反序列化之后会产生非正常结果,利用这一方法就可以实现远程执行任意代码。

在PHP中注意wakeup绕过问题。

关键词/接口/类包:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Java
ObjectInputStream.readObject
ObjectInputStream.readUnshared
XMLDecoder.readObject
Yaml.load
XStream.fromXML
ObjectMapper.readValue
JSON.parseObject
// PHP
serialize()
unserialize()
ini_set('session.serialize_handler', 'php_serialize');

修复方案: 禁止JVM执行外部命令,是一个简单有效的提高JVM安全性的办法。可以考虑在代码安全扫描时,加强对命令执行等相关代码的检测。也可在反序列化时设置白名单,对于一些只提供接口的库则可使用黑名单设置不允许被反序列化类或者提供设置白名单的接口

SQL注入漏洞

注入攻击的本质,是程序把用户输入的数据当做的Sql代码执行。这里有两个关键条件,第一是用户能够控制输入;第二是用户输入的数据被拼接到要执行的Sql代码中从而被执行。sql注入漏洞则是程序将用户输入数据拼接到了sql语句中,从而攻击者即可构造、改变sql语义从而进行攻击。

应用场景:数据库操作处使用动态拼接形式。

漏洞代码示例-1:

1
2
3
4
5
6
7
8
9
10
11
12
// Python
def getUsers(user_id=None):
conn = psycopg2.connect("dbname='××' user='××' host='' password=''")
cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
if user_id==None:
str = 'select distinct * from auth_user'
else:
str='select distinct * from auth_user where id=%d'%user_id
res = cur.execute(str)
res = cur.fetchall()
conn.close()
return res

关键词/接口/类包:

这个比较容易寻找,只要找sql查询的关键词即可。

1
2
3
4
5
select
delect
insert
update
...

修复方案:

  1. 框架所提供的Sql安全语法
  2. 避免使用动态拼接形式
  3. 采用预编译的方式。
  4. 统一编码(UTF-8)

XSS

存储型XSS和反射XSS基本没什么区别,就是中间经过了数据库。

而DOM型XSS主要关于html以及js代码中有没有相关的拼接、打印操作。

应用场景:将从客户端接受的数据未经过滤直接打印到页面上。

漏洞代码实例-1:

1
2
3
4
// Python
def xss_test(request):
name = request.GET['name']
return HttpResponse('hello %s' %(name))

修复方案: 对敏感字符进安全过滤。

XML漏洞

XML注入

原理与SQL注入基本相同,只是改成了xml的形式,例如:

1
quantity=1</quantity><price>5.0</price><quantity>1

XXE

XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。文档类型定义(DTD)的作用是定义 XML 文档的合法构建模块。DTD 可以在 XML 文档内声明,也可以外部引用。

当允许引用外部实体时,恶意攻击者即可构造恶意内容访问服务器资源,如读取passwd文件、对外发起请求、DOS等:

应用场景:XML解析一般在导入配置、数据传输接口等场景可能会用到,涉及到XML文件处理的场景可留意下XML解析器是否禁用外部实体,从而判断是否存在XXE。

关键词/接口/类包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Java
javax.xml.parsers.DocumentBuilder
javax.xml.stream.XMLStreamReader
org.jdom.input.SAXBuilder
org.jdom2.input.SAXBuilder
javax.xml.parsers.SAXParser
org.dom4j.io.SAXReader
org.xml.sax.XMLReader
javax.xml.transform.sax.SAXSource
javax.xml.transform.TransformerFactory
javax.xml.transform.sax.SAXTransformerFactory
javax.xml.validation.SchemaFactory
javax.xml.bind.Unmarshaller
javax.xml.xpath.XPathExpression
...

修复方案:

使用XML解析器时需要设置其属性,禁止使用外部实体,XML解析器的安全使用可参考文章_Prevention_Cheat_Sheet )

变量覆盖漏洞

漏洞代码示例-1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// PHP $$ 变量覆盖
<?php
show_source(__FILE__);
include "flag.php";
$_403 = "Access Denied";
$_200 = "Welcome Admin";
if ($_SERVER["REQUEST_METHOD"] != "POST")
die("BugsBunnyCTF is here :p…");
if ( !isset($_POST["flag"]) )
die($_403);
foreach ($_GET as $key => $value)
$$key = $$value;
foreach ($_POST as $key => $value)
$$key = $value;
if ( $_POST["flag"] !== $flag )
die($_403);
echo "This is your flag : ". $flag . "\n";
die($_200);
?>

由于2个foreach的代码会将$flag的值给覆盖掉,所以只能利用第一个foreach先将 $flag的值赋给$_200,然后利用die($_200)将原本的flag值打印出来。

foreach_ss

漏洞代码示例-2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// PHP $$ parse_str()
<?php
error_reporting(0);
if (empty($_GET['id'])) {
show_source(__FILE__);
die();
} else {
include ('flag.php');
$a = "This is a test.";
$id = $_GET['id'];
@parse_str($id);
if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
echo $flag;
} else {
exit('Error!');
}
}
?>

parse_str() 函数用于把查询字符串解析到变量中,如果没有array 参数,则由该函数设置的变量将覆盖已存在的同名变量。

php弱语言特性,0e123会被当做科学计数法

使用GET请求id=a[0]=240610708,这样会将a[0]的值覆盖为240610708,然后经过md5后得到0e462097431906509019562988736854与md5(‘QNKCDZO’)的结果0e830400451993494058024219903391比较都是0 所以相等,满足条件,得打flag。

关键词/接口/类包:

1
2
3
4
5
6
// PHP
extract()
import_request_variables()
parse_str()
mb_parse_str()
全局变量覆盖:register_globals为ON,$GLOBALS

逻辑漏洞

水平/垂直越权

越权漏洞可以分为水平、垂直越权两种,程序在处理用户请求时未对用户的权限进行校验,使的用户可访问、操作其他相同角色用户的数据,这种情况是水平越权;如果低权限用户可访问、操作高权限用户则的数据,这种情况为垂直越权。

应用场景:查看文章、修改密码等等。

修复方案:获取当前登陆用户并校验该用户是否具有当前操作权限,并校验请求操作数据是否属于当前登陆用户,当前登陆用户标识不能从用户可控的请求参数中获取。

批量请求

业务中经常会有使用到发送短信校验码、短信通知、邮件通知等一些功能,这类请求如果不做任何限制,恶意攻击者可能进行批量恶意请求轰炸,大量短信、邮件等通知对正常用户造成困扰,同时也是对公司的资源造成损耗。

除了短信、邮件轰炸等,还有一种情况也需要注意,程序中可能存在很多接口,用来查询账号是否存在、账号名与手机或邮箱、姓名等的匹配关系,这类请求如不做限制也会被恶意用户批量利用,从而获取用户数据关系相关数据。对这类请求在代码审计时可关注是否有对请求做鉴权、和限制即可大致判断是否存在风险。

应用场景:短信校验码、短信通知、邮件通知等。

修复方案:在服务端对同一个用户发起这类请求的频率、每小时及每天发送量在服务端做限制。

支付漏洞

应用场景:支付场景。

重复发包利用时间差:

漏洞代码示例-1:

1
2
3
4
5
6
7
8
// PHP
<?PHP
if(check_money($price)){
... // 数据库取出数据
// 花费几秒
$money = $money - $price
... // 数据库存入数据
}

修复方案:完善逻辑。

其他逻辑漏洞

这里就要根据业务功能具体问题具体分析了,可以参考下各行业漏洞点

找回密码 token http://foreversong.cn/archives/899

SSRF

SSRF形成的原因大都是由于代码中提供了从其他服务器应用获取数据的功能但没有对目标地址做过滤与限制。比如从指定URL链接获取图片、下载等。

应用场景:程序中发起HTTP请求操作一般在获取远程图片、页面分享收藏等业务场景,在代码审计时可重点关注一些HTTP请求操作函数。

关键词/接口/类包:

1
2
3
4
5
6
// Java
HttpClient.execute
HttpClient.executeMethod
HttpURLConnection.connect
HttpURLConnection.getInputStream
URL.openStream

修复方案:

  1. 使用白名单校验HTTP请求url地址
  2. 避免将请求响应及错误信息返回给用户
  3. 禁用不需要的协议及限制请求端口,仅仅允许http和https请求等

Autobinding

Autobinding-自动绑定漏洞,根据不同语言/框架,该漏洞有几个不同的叫法,如下:

  1. Mass Assignment: Ruby on Rails, NodeJS
  2. Autobinding: Spring MVC, ASP.NET MVC
  3. Object injection: PHP(对象注入、反序列化漏洞)

软件框架有时允许开发人员自动将HTTP请求参数绑定到程序代码变量或对象中,从而使开发人员更容易地使用该框架。这里攻击者就可以利用这种方法通过构造http请求,将请求参数绑定到对象上,当代码逻辑使用该对象参数时就可能产生一些不可预料的结果。

具体参考

URL重定向

由于Web站点有时需要根据不同的逻辑将用户引向到不同的页面,如典型的登录接口就经常需要在认证成功之后将用户引导到登录之前的页面,整个过程中如果实现不好就可能导致URL重定向问题,攻击者构造恶意跳转的链接,可以向用户发起钓鱼攻击。

应用场景:登录接口等

关键词/接口/类包:

1
2
3
4
// Java
sendRedirect
setHeader
forward

修复方案:

  1. 使用白名单校验重定向的url地址
  2. 给用户展示安全风险提示,并由用户再次确认是否跳转

CSRF

跨站请求伪造(Cross-Site Request Forgery,CSRF)是一种使已登录用户在不知情的情况下执行某种动作的攻击。因为攻击者看不到伪造请求的响应结果,所以CSRF攻击主要用来执行动作,而非窃取用户数据。当受害者是一个普通用户时,CSRF可以实现在其不知情的情况下转移用户资金、发送邮件等操作;但是如果受害者是一个具有管理员权限的用户时CSRF则可能威胁到整个Web系统的安全。

由于开发人员对CSRF的了解不足,错把”经过认证的浏览器发起的请求”当成”经过认证的用户发起的请求”,当已认证的用户点击攻击者构造的恶意链接后就”被”执行了相应的操作。

此类漏洞一般都会在框架中解决修复,所以在审计csrf漏洞时。首先要熟悉框架对CSRF的防护方案,一般审计时可查看增删改请求重是否有token、formtoken等关键字以及是否有对请求的Referer有进行校验。手动测试时,如果有token等关键则替换token值为自定义值并重放请求,如果没有则替换请求Referer头为自定义链接或置空。重放请求看是否可以成功返回数据从而判断是否存在CSRF漏洞。

应用场景:编辑文章,修改密码等

修复方案:

  1. Referer校验,对HTTP请求的Referer校验,如果请求Referer的地址不在允许的列表中,则拦截请求。
  2. Token校验,服务端生成随机token,并保存在本次会话cookie中,用户发起请求时附带token参数,服务端对该随机数进行校验。如果不正确则认为该请求为伪造请求拒绝该请求。
  3. Formtoken校验,Formtoken校验本身也是Token校验,只是在本次表单请求有效。
  4. 对于高安全性操作则可使用验证码、短信、密码等二次校验措施
  5. 增删改请求使用POST请求

二次漏洞

攻击者提交的恶意的代码不是直接通过一个变量提交漏洞函数而是通过变量转化或者中转,最终提交到漏洞函数,例如通过SQL注射漏洞转化、通过编码/解码中转变量、或者其他方式等等。

比如先将用户提交的数据储存进数据库,然后再读取数据库中的数据传入命令执行或者文件读取的函数中去,造成漏洞的产生等等。

应用场景:多样化。

修复方案:在处理逻辑上进行分析,在交给敏感函数处理之前严格校验。

第三方组件安全

这个比较好理解,诸如Struts2、不安全的编辑控件、XML解析器以及可被其它漏洞利用的如commons-collections:3.1等第三方组件,这个可以在程序pom文件中查看是否有引入依赖。即便在代码中没有应用到或很难直接利用,也不应该使用不安全的版本,一个产品的周期很长,很难保证后面不会引入可被利用的漏洞点。

修复方案:使用最新或安全版本的第三方组件。

弱类型

PHP 的弱类型问题由来已久,

出现这些问题的,具体原因其实有很多,除了 intval 之类的类型转换,也有其他的。

PHP使用strcmp比较图:

strcmp

1
2
3
4
5
6
7
8
9
10
<?php
// PHP
if(in_array($_GET['id'], array(1,2,3,4))){
$sql = "SELECT name from user Where id = '".$_GET['id']."'";
echo $sql;
}
// in_array() : 比较之前会自动转换类型
// ?id=1' union select '1

再比如:
双等于== 和三等于=== 的问题:
双等于会在变量比较时,进行类转换,与in_array() 是一样的问题。
三等于是type和value的双重比较,相比之下更加安全。

其他

最后还有其他临时想到的安全问题:

  1. 对不可信的字符串进行正则匹配造成的DOS.
  2. 代码中的敏感信息(数据库密码、密钥)直接以硬编码的形式写进代码、配置文件中。
  3. 日志拼接时将用户可控的字符直接输出。
  4. 异常处理不好导致信息泄露。
  5. ……

练习

对于代码审计的入门,可先实践一遍Damn Vulnerable Web App(DVWA)和Sibria Exploit Kit的漏洞发掘与利用。

DVWA是基于PHP并汇总了各类漏洞的一套测试环境,在其中能够看到Web应用中许多常见的错误。Siberia Exploit Kit是一个被许多犯罪份子用来完成大量攻击的”犯罪套件”,它包括了一个浏览器利用包和一个用来管理受害主机的控制面板。Siberia包含的几种基于POST的身份认证漏洞允许攻击者获得管理员权限并接管服务器所在的主机。

  1. OWASP Broken Web Apps
  2. Siberia Crimeware Pack口令: infected

之后便可去网上寻找下载CMS等进行进一步的学习。

案例

这里附一个大佬们挖出的漏洞phpcms v9.6.1任意文件读取漏洞

在phpcms中,有一个file_down函数用来下载文件的,该函数位于/phpcms/libs/functions/global.func.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function file_down($filepath, $filename = '') {
if(!$filename) $filename = basename($filepath);
if(is_ie()) $filename = rawurlencode($filename);
$filetype = fileext($filename);
$filesize = sprintf("%u", filesize($filepath));
if(ob_get_length() !== false) @ob_end_clean();
header('Pragma: public');
header('Last-Modified: '.gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: pre-check=0, post-check=0, max-age=0');
header('Content-Transfer-Encoding: binary');
header('Content-Encoding: none');
header('Content-type: '.$filetype);
header('Content-Disposition: attachment; filename="'.$filename.'"');
header('Content-length: '.$filesize);
readfile($filepath);
exit;
}

而调用他的地方是:

phpcms1

该函数就是一个正常的文件下载的函数,而调用这个函数的地方位于phpcms\modules\content\down.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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
class down {
// ......
public function init() {
$a_k = trim($_GET['a_k']);
if(!isset($a_k)) showmessage(L('illegal_parameters'));
$a_k = sys_auth($a_k, 'DECODE', pc_base::load_config('system','auth_key'));
if(empty($a_k)) showmessage(L('illegal_parameters'));
unset($i,$m,$f);
$a_k = safe_replace($a_k);
parse_str($a_k);
if(isset($i)) $i = $id = intval($i);
if(!isset($m)) showmessage(L('illegal_parameters'));
if(!isset($modelid)||!isset($catid)) showmessage(L('illegal_parameters'));
if(empty($f)) showmessage(L('url_invalid'));
$allow_visitor = 1;
$id = intval($id);
$modelid = intval($modelid);
$catid = intval($catid);
$MODEL = getcache('model','commons');
$tablename = $this->db->table_name = $this->db->db_tablepre.$MODEL[$modelid]['tablename'];
$this->db->table_name = $tablename.'_data';
$rs = $this->db->get_one(array('id'=>$id));
$siteids = getcache('category_content','commons');
$siteid = $siteids[$catid];
$CATEGORYS = getcache('category_content_'.$siteid,'commons');
$this->category = $CATEGORYS[$catid];
$this->category_setting = string2array($this->category['setting']);
//检查文章会员组权限
$groupids_view = '';
if ($rs['groupids_view']) $groupids_view = explode(',', $rs['groupids_view']);
if($groupids_view && is_array($groupids_view)) {
$_groupid = param::get_cookie('_groupid');
$_groupid = intval($_groupid);
if(!$_groupid) {
$forward = urlencode(get_url());
showmessage(L('login_website'),APP_PATH.'index.php?m=member&c=index&a=login&forward='.$forward);
}
if(!in_array($_groupid,$groupids_view)) showmessage(L('no_priv'));
} else {
//根据栏目访问权限判断权限
$_priv_data = $this->_category_priv($catid);
if($_priv_data=='-1') {
$forward = urlencode(get_url());
showmessage(L('login_website'),APP_PATH.'index.php?m=member&c=index&a=login&forward='.$forward);
} elseif($_priv_data=='-2') {
showmessage(L('no_priv'));
}
}
//阅读收费 类型
$paytype = $rs['paytype'];
$readpoint = $rs['readpoint'];
if($readpoint || $this->category_setting['defaultchargepoint']) {
if(!$readpoint) {
$readpoint = $this->category_setting['defaultchargepoint'];
$paytype = $this->category_setting['paytype'];
}
//检查是否支付过
$allow_visitor = self::_check_payment($catid.'_'.$id,$paytype,$catid);
if(!$allow_visitor) {
$http_referer = urlencode(get_url());
$allow_visitor = sys_auth($catid.'_'.$id.'|'.$readpoint.'|'.$paytype).'&http_referer='.$http_referer;
} else {
$allow_visitor = 1;
}
}
if(preg_match('/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(\.|$)/i',$f) || strpos($f, ":\\")!==FALSE || strpos($f,'..')!==FALSE) showmessage(L('url_error'));
if(strpos($f, 'http://') !== FALSE || strpos($f, 'ftp://') !== FALSE || strpos($f, '://') === FALSE) {
$pc_auth_key = md5(pc_base::load_config('system','auth_key').$_SERVER['HTTP_USER_AGENT'].'down');
$a_k = urlencode(sys_auth("i=$i&d=$d&s=$s&t=".SYS_TIME."&ip=".ip()."&m=".$m."&f=$f&modelid=".$modelid, 'ENCODE', $pc_auth_key));
$downurl = '?m=content&c=down&a=download&a_k='.$a_k;
} else {
$downurl = $f;
}
include template('content','download');
}
public function download() {
$a_k = trim($_GET['a_k']);
$pc_auth_key = md5(pc_base::load_config('system','auth_key').$_SERVER['HTTP_USER_AGENT'].'down');
$a_k = sys_auth($a_k, 'DECODE', $pc_auth_key);
if(empty($a_k)) showmessage(L('illegal_parameters'));
unset($i,$m,$f,$t,$ip);
$a_k = safe_replace($a_k);
parse_str($a_k);
if(isset($i)) $downid = intval($i);
if(!isset($m)) showmessage(L('illegal_parameters'));
if(!isset($modelid)) showmessage(L('illegal_parameters'));
if(empty($f)) showmessage(L('url_invalid'));
if(!$i || $m<0) showmessage(L('illegal_parameters'));
if(!isset($t)) showmessage(L('illegal_parameters'));
if(!isset($ip)) showmessage(L('illegal_parameters'));
$starttime = intval($t);
if(preg_match('/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(\.|$)/i',$f) || strpos($f, ":\\")!==FALSE || strpos($f,'..')!==FALSE) showmessage(L('url_error'));
$fileurl = trim($f);
if(!$downid || empty($fileurl) || !preg_match("/[0-9]{10}/", $starttime) || !preg_match("/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/", $ip) || $ip != ip()) showmessage(L('illegal_parameters'));
$endtime = SYS_TIME - $starttime;
if($endtime > 3600) showmessage(L('url_invalid'));
if($m) $fileurl = trim($s).trim($fileurl);
if(preg_match('/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(\.|$)/i',$fileurl) ) showmessage(L('url_error'));
//远程文件
if(strpos($fileurl, ':/') && (strpos($fileurl, pc_base::load_config('system','upload_url')) === false)) {
header("Location: $fileurl");
} else {
if($d == 0) {
header("Location: ".$fileurl);
} else {
$fileurl = str_replace(array(pc_base::load_config('system','upload_url'),'/'), array(pc_base::load_config('system','upload_path'),DIRECTORY_SEPARATOR), $fileurl);
$filename = basename($fileurl);
//处理中文文件
if(preg_match("/^([\s\S]*?)([\x81-\xfe][\x40-\xfe])([\s\S]*?)/", $fileurl)) {
$filename = str_replace(array("%5C", "%2F", "%3A"), array("\\", "/", ":"), urlencode($fileurl));
$filename = urldecode(basename($filename));
}
$ext = fileext($filename);
$filename = date('Ymd_his').random(3).'.'.$ext;
$fileurl = str_replace(array('<','>'), '',$fileurl);
file_down($fileurl, $filename);
}
}
}
// ......

/phpcms/libs/functions/global.func.php函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function safe_replace($string) {
$string = str_replace('%20','',$string);
$string = str_replace('%27','',$string);
$string = str_replace('%2527','',$string);
$string = str_replace('*','',$string);
$string = str_replace('"','&quot;',$string);
$string = str_replace("'",'',$string);
$string = str_replace('"','',$string);
$string = str_replace(';','',$string);
$string = str_replace('<','&lt;',$string);
$string = str_replace('>','&gt;',$string);
$string = str_replace("{",'',$string);
$string = str_replace('}','',$string);
$string = str_replace('\\','',$string);
return $string;
}

把其他无关代码去掉,就是下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
init(){
$a_k = trim($_GET['a_k']);
$a_k = sys_auth($a_k, 'DECODE', pc_base::load_config('system','auth_key'));
$a_k = safe_replace($a_k);
parse_str($a_k);
$pc_auth_key = md5(pc_base::load_config('system','auth_key').$_SERVER['HTTP_USER_AGENT'].'down');
$a_k = urlencode(sys_auth("i=$i&d=$d&s=$s&t=".SYS_TIME."&ip=".ip()."&m=".$m."&f=$f&modelid=".$modelid, 'ENCODE', $pc_auth_key));
$downurl = '?m=content&c=down&a=download&a_k='.$a_k;
down();
}
download(){
$a_k = trim($_GET['a_k']);
$pc_auth_key = md5(pc_base::load_config('system','auth_key').$_SERVER['HTTP_USER_AGENT'].'down');
$a_k = sys_auth($a_k, 'DECODE', $pc_auth_key);
$a_k = safe_replace($a_k);
parse_str($a_k); // 函数用于把查询字符串解析到变量中,如果没有array 参数,则由该函数设置的变量将覆盖已存在的同名变量。
$fileurl = trim($f);
if($m) $fileurl = trim($s).trim($fileurl);
if(preg_match('/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(\.|$)/i',$fileurl) ) showmessage(L('url_error'));
$fileurl = str_replace(array('<','>'), '',$fileurl);
file_down($fileurl, $filename);
}

注意点:

  1. php原生parse_str方法,会自动进行一次urldecode,第二个参数为空,则执行类似extract操作。
  2. 原生empty方法,对字符串””返回true。
  3. phpcms中sys_auth是对称加密且在不知道auth_key的情况下理论上不可能构造出有效密文。

具体分析如下:

参数由$_GET['a_k']传入,经过safe_replace安全检查后,解析变量,然后检查后缀名,最后去除<,>这些符号,最终运行下载函数。
最终$_GET['a_k']传入pad=x&i=1&modelid=1&catid=1&d=1&m=1&f=.p<hp&s=index&pade=
由于safe_replce的存在所以<会被过滤掉,前置知识中我已经说到parse_str会自动urldecode一次。
所以可以构造pad=x&i=1&modelid=1&catid=1&d=1&m=1&f=.p%3chp&s=index&pade=
我们发现在init方法中会safe_replace一次,和parse_str一次。
那么最终编码到download $a_k中的数据实际还是<,而download方法中也会safe_replace和parse_str一次。
所以我们要确保在init方法编码的时候是%3c即可,对%3c进行一次urlencode,构造d=1&m=1&f=.p%253chp&s=index
当然要读取别的目录的,那同样对目录路径进行编码。
最终poc:

  1. 第一步:获取一个身份

请求http://127.0.0.1:80/phpcms/index.php?m=wap&c=index&a=init&siteid=1获取一个身份。

  1. 第二步:获取加密值

请求
http://127.0.0.1:80/phpcms/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=pad%3Dx%26i%3D1%26modelid%3D1%26catid%3D1%26d%3D1%26m%3D1%26s%3Dcaches/configs/database%26f%3D.p%25253chp

POST: userid_flash = _siteid

  1. 第三步:获取加密值

请求http://127.0.0.1:80/phpcms/index.php?m=content&c=down&a=init&a_k= + _att_json

phpcms-z

总结

随着开发人员的安全意识的不断提高,Web应用的安全机制也不断的增加,从代码上一眼就能看出的漏洞已经越来越少了,想要找出漏洞更需要对代码、框架的进一步理解,分析,例如:文章
总的来说,不管是什么漏洞,在哪里出现,它们都有一个共同点,那就是危险函数中使用了可控参数,这些参数可被恶意用户直接或者间接控制,从函数中传进来,或者经过简单的编码,截断等处理直接进入危险函数,导致了危险行为。如果在执行危险函数前对这些可控参数进行一定判断,如必须是数字,路径必须存在,去掉某些特殊符号等则避免了问题的出现。

参考文章-1
参考文章-2
参考文章-3
参考文章-4
参考文章-5
参考文章-6
参考文章-7
参考文章-8
参考文章-9
参考文章-10