<
代码审计基础
>
上一篇

SQL注入总结
下一篇

CTF中PHP考察点
代码审计基础

一、审计方向

  1. 从业务层进行有序测试
    根据http请求跟踪响应代码,对于用户可以控制的变量需仔细跟踪
  2. 从程序实现上逆向测试
    从配置文件入手(Application-context.xml、struts.xml、web.xml),搜索sql语句关键字,关键变量($,+)等

二、审计点

2.1 认证会话管理

  1. 验证码
    图形验证码一般是防止使用程序恶意注册、暴力破解用户名密码或者批量发帖而设置的。在页面初试化时服务器向页面发送一个随机字符串,同时在 session 里也保存一份,当用户提交时将随机数一起 post 到后台,通过与 session 中保存的值对比,如果不相同,则有可能是恶意攻击。
    注意点
    • 验证码有效性是否只有一次(及时清除验证过的验证码)
    • 验证码是否设置了超时机制
    • 应先验证验证码后进行其他逻辑

验证码安全编码规范

String userName = request.getParameter("adminname");
String userPwd = request.getParameter("adminpwd");
//从session中取得验证码
String validateCode=(String)request.getSession().getAttribute("rand");
//获取用户输入的验证码
String userInput=request.getParameter("validateCode");

if(userName__null||userPwd__null)
	return;

if(validateCode!=null) {
	if(!validateCode.equalsIgnoreCase(userInput)) {
		out.print("<script>alert('验证错误! ');window.location='AdminLogin.jsp'</script>"); 
		//移除使用过的验证码
		request.getSession().removeAttribute("rand");
		return;
	} else {
		request.setAttribute("message", "验证正确");
	}
	
	boolean loginValid=dao.LoginStatus(userName, userPwd);

	if(loginValid) {
		request.getSession().invalidate();
		request.getSession().setAttribute("AdminName", userName);
		out.print("<script>alert('登录成功,跳转到首页');window.location='AdminIndex.jsp'</script>");
	} else {
		out.print("<script>alert('用户名或密码错误!');window.location='AdminLogin.jsp'</script>");
	}
	
	//移除使用过的验证码
	request.getSession().removeAttribute("rand");
}

  1. 用户登陆认证
    注意点
    • 用户登陆认证应检查用户名和密码的合法性。
    • 对于用户名错误和密码错误的提示信息应统一,降低账号、密码被猜解的风险。
    • 用户唯一性(用户注册时检查)。
      用户登录安全编码规范
public boolean checkUser(String username,String password) {
	try { 
		//使用预编译的方式防止产生注入
		pstmt=ct.prepareStatement("select * from userTable where username=? and password=?");
		pstmt.setString(1, username);

		//密码加密
		pstmt.setString(2, Entrypt. string2MD5(password,salt));
		ResultSet rs=pstmt.executeQuery();
		User user=new User();
		while(rs.next()) {
			user.setId(rs.getInt(1));
			user.setUsername(rs.getString(2));
			user.setPassword(rs.getString(3));

			//更新session数据
			HttpSession session=dhUtil.changeSessionIdentifier(request);
			session. .setAttribute("username", username);
			return user;
		}
		return null;
	} catch(Exception e) {
		return null;
	}
}

  1. 注销
    注意点
    • 退出时要清除会话信息
      代码合规示例
public void logout()  {
	ESAPI.httpUtilities().killCookie( ESAPI.currentRequest(),ESAPI.currentResponse(),HTTPUtilities.REMEMBER_TOKEN_COOKIE_NAME );
	HttpSession session = ESAPI.currentRequest().getSession(false);
	if (session != null)  {
		removeSession(session);
		session.invalidate();
	}
	ESAPI.httpUtilities().killCookie(ESAPI.currentRequest(),ESAPI.currentResponse(),"JSESSIONID");
	loggedIn = false;
	logger.info(Logger.SECURITY_SUCCESS, "Logout successful" );
	ESAPI.authenticator().setCurrentUser(User.ANONYMOUS);
	ESAPI.httpUtilities().sendRedirect(“登录页面”);
}

  1. 认证失败次数限制
  2. url重定向
    Web应用中经常需要指定完成当前页面操作之后下一个页面的请求地址。常见的例如:登录操作完成之后,返回指定页面或者返回登录操作前页面。如果返回地址可被攻击者控制,可能导致受害者访问恶意网站、钓鱼网站的链接。
    注意点
Java:
response.sendRedirect(request.getParameter("url"))
PHP:
$redirect_url = $_GET['url'];
header("Location: " . $redirect_url)
.NET:
string redirect_url = request.QueryString["url"];
Response.Redirect(redirect_url);
Django:
redirect_url = request.GET.get("url")
HttpResponseRedirect(redirect_url)
Flask:
redirect_url = request.form['url']
redirect(redirect_url)
Rails:
redirect_to params[:url]

写代码时没有考虑过任意URL跳转漏洞,或者根本不知道/不认为这是个漏洞;
写代码时考虑不周,用取子串、取后缀等方法简单判断,代码逻辑可被绕过;
对传入参数做一些奇葩的操作(域名剪切/拼接/重组)和判断,适得其反,反被绕过;
原始语言自带的解析URL、判断域名的函数库出现逻辑漏洞或者意外特性,可被绕过;
原始语言、服务器/容器特性、浏览器等对标准URL协议解析处理等差异性导致被绕过;

  1. sso认证登录
  2. session 安全
    注意点
    • 防止暴力猜解
    • 会话劫持,每次认证后应启用全新用户ID
    • Session超时检查
    • session清理
  3. cookie管理
    注意点
    • 是否会在Cookie中存储明文或简单编码/加密(base64编码)过的密码
    • 是否会在Cookie中存储应用的特权标识
    • 是否设置了Cookie的有效域和有效路径
    • 是否设置了合适的Cookie有效时间

2.2 输入输出

  1. sql注入
    注意点
    • 是否存在全局过滤器,过滤器配置、过滤函数等
    • 过滤器是否可以过滤所有查询请求
    • 请求是否都按要求经过过滤器处理
    • 过滤器的过滤是否符合要求
    • 初期检查可以依据PHPIDS的规则库,后期根据收集的情况予以补充
    • 是否使用了预编译
    • 预查询是指在将数据传入SQL语句前明确指定传输数据的类型,以执行必要的转换。在Java中预查询的调用方式为prepareStatement。
    • 是否存在SQL语句拼接
      某些特殊的查询(特别复杂的组合查询)难免用到SQL语句拼接,遇到这种情况,就需要检查拼接是否有可能导致注入。 —
  2. 跨站攻击
    注意点
    • 是否存在全局XSS过滤器(论坛的过滤库)
    • 是否存在需过滤和不需过滤两种输出,页面是否控制恰当(*)
    • 某些情况下可能存在两种输出,文本输出和富文本(HTML)输出,要强制文本输出,只需要调用HTMLEncode()对内容进行编码后输出即可;但是富文本本身就需要使用html来进行格式的控制,简单的编码就无法使用,这时需要在此类内容从客户端输入(用户提交)或输出给客户端(显示)时进行危险代码过滤。
    • 输出的时候是否进行编码(HTML、JS)

  3. csrf防护
    Web 表单是否使用了Token(或验证码)
    在操作时不严格区分GET和POST,在没有Token(或验证码)的辅助下很容易导致CSRF的发生 代码示例
<form action="http://spdb.com/modify.jsp" method="POST">
	<input name="email">
	<input name="tel">
	<input name="realname">
	<input name="userid">
	<input type="submit">
</form>
//因为表单中没有token,以上代码中存在csrf漏洞

  1. 文件上传 注意点
    • 是否限制文件允许上传的扩展名(白名单)
    • 是否对上传文件进行重命名
    • 上传目录限制级目录权限限制
      代码示例
protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException 
{
	response.setContentType("text/html");
	PrintWriter out = response.getWriter();
	String contentType = request.getContentType();
	int ind = contentType.indexOf("boundary=");
	String boundary = contentType.substring(ind+9);
	String pLine = new String();
	String uploadLocation = new String(UPLOAD_DIRECTORY_STRING);
	//判断contentType是否是multipart/form-data

	if (contentType != null && contentType.indexOf("multipart/form-data") != -1) 
	{
		//从HttpHeader中提取文件名
		BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
		...
		pLine = br.readLine();
		String filename = pLine.substring(pLine.lastIndexOf("\\"),pLine.lastIndexOf("\""));
		...
		//把文件输出到上传目录
		try  {
			BufferedWriter bw = new BufferedWriter(new FileWriter(uploadLocation+filename, true));
			for (String line; (line=br.readLine())!=null; )  {
				if (line.indexOf(boundary) __ -1)  {
					bw.write(line);
					bw.newLine();
					bw.flush();
				}
			}           //循环结束
			bw.close();
		} 
		catch (IOException ex) {...}
		//输出成功加载并返回的HTML页面
	}
	//输出成功加载并返回的HTML页面
	else
	{...}
}

5.文件下载
注意点

protected void doPost(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException 
{
	try  {
		byte data[] = new byte[1];
		//取得用户提交的图片文件名,没有检测是否为图片,也没有检测是否包含../../目录跳转的字符
		String imgName = request.getParameter("imgName");
		String imgKey = MD5Encrypt.MD5(imgName);        //本地

		if (imageCache.containsKey(imgKey))  {
			data = (byte[]) imageCache.get(imgKey);
		}  else  {
			String imagePath = Consts.IMG_LOCAL_PAHT + imgName;
			//没有对该参数进行严格的验证和过滤,就拼接成完整的图片路径
			InputStream inputStream = null; 
			File imageFile = new File(imagePath);
			logger.debug(imagePath + " " + imageFile.exists());
			if (imageFile.exists() && imageFile.isFile())  {
				inputStream = new FileInputStream(imagePath);
				int i = inputStream.available();
				data = new byte[i];
				inputStream.read(data);
				inputStream.close();
				imageCache.put(imgKey, data);
			}  else  {
				……
			}
		}
		//将文件内容输出到客户端
		response.setContentType("image/*");
		OutputStream outputStream = response.getOutputStream();
		outputStream.write(data);
		outputStream.close();
	}
}

6.重定向和转发
注意点

public class RedirectServlet extends HttpServlet  {
	protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException 
	{
		String query = request.getQueryString();
		
		if (query.contains("url"))  {
			String url = request.getParameter("url");
			response.sendRedirect(url);
		}
	}
}
//服务端从客户端接收参数url,直接指定其为重定向地址

2.3 授权管理

1.越权漏洞 获取数据时权限未验证,可以控制查询数据

2.4 业务安全

1.逻辑漏洞 交易重放
密码重置
找回密码
修改密码
短信验证码发送间隔控制

2.5 框架相关漏洞

1.iBatis(MyBatis)框架
代码示例

代码示例:mapper文件
select * from messages where username=#{username}
//这种写法不会产生SQL注入

select * from user where username like '%$username$%'
//这种like写法会产生SQL注入
//在ibatis中,“#”会对传入的变量进行转义,“$”则是直接拼接

hibernate
代码示例

代码示例:java代码:
Sring hql=”from User where username=’”+name+”’ and password=’”+password+”’;
User u = session.find(hql);
//在hibernate中使用hql时,要尽量避免使用拼接方式

  1. 富文本编辑器

三、注意函数

预定义变量[常规外部提交的变量]:

除了$_GET,$_POST,$_Cookie的提交之外,还来源于$_SERVER,$_ENV, $_SESSION 等register_globals = on [未初始化的变量] 当On的时候,传递过来的值会被直接的注册为全局变量直接使用,而Off的时候,我们需要到特定的数组里去得到它,PHP » 4.20 默认为off

变量覆盖[未初始化及覆盖前定义的变量]:

如:$$使用不当、遍历初始化变量、 extract() 、parse_str()等 文件包含包含漏洞:require、include、require_once、include_once

变量的传递与存储[中转的变量]:

存储于数据库、文件[如配置、缓存文件等

代码执行执行任意代码漏洞:

eval()、assert()、preg_replace()、create_function()

命令执行执行任意命令漏洞:

exec()、passthru()、proc_open()、shell_exec()、system()、popen()

文件系统操作文件(目录)读写等漏洞:

file_get_contents、file_put_contents、fopen、readfile

数据库操作SQL注入漏洞:

select from、mysql_connect、mysql_query、mysql_fetch_row

XSS漏洞:

print、print_r、echo、print、sprintf、die、Var_dump、var_export

Top
Foot