Momo Java Security Coding Specification
https://github.com/momosecurity/rhizobia_J
Article Directory
- JAVA Secure Coding Specification
-
- 1. Basic principles of secure coding
- 2. Security coding methods corresponding to common vulnerabilities
JAVA Secure Coding Specification
1. Basic principles of secure coding
1.1 All input data is harmful
Direct data input:
For the data entered by the user through GET, POST, COOKIE, REQUEST, etc. and the data source provided by the framework, that is, all the variables transmitted from the client in the communication protocol, whether it is the data manually filled in by the user or the client browses The data automatically filled in by the device or operating system may cause security problems and require strict security checks.
Indirect input data: data
obtained from databases, files, networks, internal APIs, etc., that is, some data that are not directly derived from users, but are not constant data defined in the program. For example, the user's input is converted and output to a database or file after layer by layer, and when it is reused later, the data obtained at this time is still untrustworthy, and strict security checks are also required.
1.2 Security configuration that does not depend on the operating environment
You can't expect the security options of the configuration file, you must put the program in the most insecure configuration for consideration.
1.3 Security control measures are implemented in the final implementation stage
Every security problem has its own reasons. For example, the reason for SQL injection is the splicing of SQL statement parameters. Therefore, to prevent SQL injection problems, it is necessary to safely handle the parameters before the SQL statement is executed, because at this time, the expected parameter data type, data range, etc. can be determined.
1.4 Minimization
The principle of minimization applies to all security-related fields. The main performance in code security is:
1. Minimize user input. Use as little user input as possible.
2. The user input range is minimized. The whitelist strategy should be used when filtering parameters, and the validity of the parameters can be checked for the parameters that can be clearly defined, such as Email, card number, ID number, etc.
3. Minimize return information. The program error information should be shielded from the user, and the original error information should not be directly returned to the user side.
1.5 Failed termination
When performing security checks on the data submitted by the user, if the data does not meet the requirements, the execution of the business should be terminated, and do not try to modify and convert the parameters submitted by the user to continue execution.
2. Security coding methods corresponding to common vulnerabilities
Command injection
Positive solution coding method:
- Exactly match user submitted data
String ip = request.getParameter("ip");
if(null==ip){
//handle error
}
Boolean ret = Pattern.matches("((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))", ip);
if(!ret){
//handle error
}
String[] cmd = new String[]{"ping", "-c", "2", ip};
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(cmd);
- Use whitelist
String dir=request.getParameter("dir");
if(null==dir){
//handle error
}
switch (dir){
case "test1":dir="test1";
break;
case "test2":order_by="test2";
break;
default:order_by="test";
}
Runtime runtime=Runtime.getRuntime();
Process process=runtime.exec(new String[]{"ls ", dir});
int result=process.waitFor();
//do something
Code injection
Positive solution coding method:
Whitelist should be used:
public Object fix(HttpServletRequest request,Map<String, Class<?>> whiteList ,org.apache.log4j.Logger logger) {
Object obj = null;
try {
String className = request.getParameter("className");
if(null==className){
//handle error
}
if (whiteList.containsKey(className)) { //白名单
obj = whiteList.get(className).newInstance();
}
} catch (InstantiationException e) {
//do something
}
return obj;
}
SQL injection
Positive solution coding method:
Parameterized query should be used
a. Parameterized query method:
jdbc
PreparedStatement should be used:
HttpServletRequest request = ...;
String userName = request.getParameter("name");
if(null==userName){
//handle error
}
Connection con = ...
String query = "SELECT * FROM Users where user=?";
PreparedStatement pre=conn.prepareStatement(query);
pre.setString(1, userName);
pre.execute();
mybatis
The wording of "#" should be used:
<select id="getByPage" resultType="com.domain.Users" parameterType="com.Param">
SELECT
username,id
FROM tb_users
WHERE isdeleted=1
<if test="name!=null and name!=''">
AND nickname LIKE CONCAT('%', #{name}, '%')
</if>
ORDER BY
createtime DESC
limit #{fromIndex},#{count}
</select>
note:
Regardless of whether the project uses the framework, the user parameters need to use the table name, field name or involve order by, group by, limit operations, parameterized query will make the table name, field name lose its original meaning
- Use methods in java security SDK
String columnName = request.getParameter("columnName");
if(null==columnName){
//handle error
}
String columnNameEncode = sqlTool.mysqlSanitise(columnName, true);
query = "SELECT NAME FROM users order by " + columnNameEncode ;
- Use whitelist processing:
switch (columnName){
case "name":columnName="name";
break;
case "num":columnName="num";
break;
default:columnName="id";
}
Mongo injection
Positive solution coding method:
Can not directly splice parameters, use BasicDBObject
String name = request.getParameter(”name");
if(null==name){
//handle error
}
BasicDBObject databaseQuery = new BasicDBObject("name", name);
DBCursor cursor = characters.find(databaseQuery);
try {
while(cursor.hasNext()) {
System.out.println(cursor.next());
}
} finally {
cursor.close();
}
XXth
Positive solution coding method:
- When parsing XML data, the parsing of DTDs (doctypes) parameters should be restricted:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
String FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
dbf.setFeature(FEATURE, true);
}catch (Exception e) {
// This should catch a failed setFeature feature
}
Xpath injection
Positive solution coding method:
Queries that should be parameterized using Xpath:
DocumentBuilderFactory builderFactory=DocumentBuilderFactory.newInstance();
builderFactory.setNamespaceAware(true);
DocumentBuilder builder=builderFactory.newDocumentBuilder();
Document document =builder.parse(new File(filepath));
XPathFactory factory=XPathFactory.newInstance();
XPath path=factory.newXPath();
String statement="/user[loginID/text()=$username and password/text()=$password]/text()"; //$username和$password占位
SimpleVariableResolver variableResolver = new SimpleVariableResolver();
variableResolver.addVariable(new QName("password"), password); //参数绑定
variableResolver.addVariable(new QName("username"), username); //参数绑定
path.setXPathVariableResolver(variableResolver);
XPathExpression xPathExpression = path.compile(statement);
XSS
Positive solution coding method:
It should be output after encoding. When outputting to different html tags or attributes on the front end, different encoding methods are used.
- When using ESAPI:
//参数输出到html实体, <div>..xssinput..</div>
String safe = ESAPI.encoder().encodeForHTML(xssInput);
//参数输出到html标签的属性, <div attr=.. xssinput..>content</div>
String safe = ESAPI.encoder().encodeForHTMLAttribute(xssInput);
//参数输出到JavaScript中, <script>x='...xssInput...'</script>
String safe = ESAPI.encoder().encodeForJavaScript(xssInput);
//富文本
ESAPI.validator(). getValidSafeHTML()
- When using the Spring framework, you can use the HtmlUtils.htmlEscape code that comes with the framework to output to html entities:
@RequestMapping("/xsstest")
public String xssTest(@RequestParam("id") String id, Model model){
id=HtmlUtils.htmlEscape(id);
model.addAttribute("id",id);
return "index";
}
- When the framework is not used, the StringEscapeUtils.escapeHtml encoding of the commons-lang library can be used to output to the html entity:
<%
String id=request.getParameter("id");
out.println(StringEscapeUtils.escapeHtml(id));
%>
CSRF
Positive solution coding method:
- After the web passport is authenticated, csrf_token will be implanted in the cookie. For such security issues, the front-end should obtain the csrf_token from the cookie, and submit the request containing the csrf_token value by POST. The code is as follows:
function getCookie() {
var value = "; " + document.cookie;
var parts = value.split("; csrf_token=");
if (parts.length == 2)
return parts.pop().split(";").shift();
}
$.ajax({
type: "post",
url: "/xxxx",
data: {csrf_token:getCookie()},
dataType: "json",
success: function (data) {
if (data.ec == 200) {
//do something
}
}
});
The backend should extract the csrf_token parameter value from the POST request body for verification. The code is as follows:
public boolean isCSRFProtectPassed(String session,String csrf_token){
if (null==session || null==csrf_token){
return false;
}
if (session.length()!=32 || csrf_token.length()!=32){
return false;
}
if (csrf_token.equals(getCSRFTokenBySession(session))){
return true;
}
return false;
}
The getCSRFTokenBySession method is implemented as follows:
public String getCSRFTokenBySession(String session){
return md5(session);
}
URL redirection vulnerability
Positive solution coding method:
The server should prevent unsafe redirects and jumps according to specific business requirements:
- If you only want to jump in the current domain, or the links after the jump are relatively few and relatively fixed, then the parameters should be whitelisted on the server side, and URLs in the non-whitelist are prohibited from being redirected;
String index=request.getParameter("index");
if(null==index){
//handle error
}
switch (index){
case "1": url="https://www.trust1.com";
break;
case "2": url="https://rule.trust1.com";
break;
default:url="https://www.trust1.com";
}
response.sendRedirect(url);
- If because of business needs, the links after the jump will often change and there are more, you should make an intermediate jump page to remind users that they will jump to other websites, please pay attention to prevent phishing attacks.
SSRF
Positive solution coding method:
Such security issues should limit the requested URL on the server side: the server side maintains a mapping relationship for a resource request list, and the server side obtains the actual requested resource from the mapping relationship according to the request parameters submitted by the client. At the same time, requests for private address segments and intranet domain names should be prohibited.
Arbitrary file traversal
Positive solution coding method:
The whitelist should be used to control the path:
String directory=request.getParameter("directory");
if(null==directory){
//handle error
}
switch (directory){
case "./image": directory="./image";
break;
case "./page": directory="./page";
break;
...
default:directory="./image";
}
while(line = readFile(directory))
{
//do something
}
File Upload
Positive solution coding method:
- Verify upload file size
- Whether the file type meets the requirements
- The original file name in the parameter cannot be used directly, the file name should be randomly generated, and the suffix should be limited
- Save to file server
private Long FILE_MAX_SIZE = 100L*1024*1024;//100M
@RequestMapping(value = "/upload", method = POST)
@ResponseBody
public String upload(@RequestParam("file") MultipartFile file) {
if(null == file){
//handle error
}
Long filesize = file.getSize();
if(FILE_MAX_SIZE<filesize){
//handle error
return "error";
}
String file_name = file.getOriginalFilename();
String[] parts = file_name.split("\\.");
String suffix = parts[parts.length - 1];
switch (suffix){
case "jpeg":
suffix = ".jpeg";
break;
case "jpg":
suffix = ".jpg";
break;
case "bmp":
suffix = ".bmp";
break;
case "png":
suffix = ".png";
break;
default:
//handle error
return "error";
}
if(!file.isEmpty()) {
long now = System.currentTimeMillis();
File tempFile = new File(now + suffix);
FileUtils.copyInputStreamToFile(file.getInputStream(), tempFile);
//将tempFile保存到文件服务器中,然后删除tempFile
}
return "OK";
}
Deserialization vulnerability
Positive solution coding method:
- For such problems, you should override the resolveClass method of the ObjectInputStream class to verify the class name of the object to be deserialized:
public final class SecureObjectInputStream extends ObjectInputStream{
public SecureObjectInputStream() throws IOException{
super();
}
public SecureObjectInputStream(InputStream in) throws IOException{
super(in);
}
protected Class<?> resolveClass(ObjectStreamClass desc) throws ClassNotFoundException, IOException{
if (!desc.getName().equals("java_security.Person")) {
throw new ClassNotFoundException(desc.getName()+" not found");
}
return super.resolveClass(desc);
}
}
- Fastjson and jackson also have problems with deserialization, and the following versions should be used:
- fastjson, version 1.2.46 and above
- jackson, 2.9.8 and above
WebSocket hijacking
Positive solution coding method:
The server should verify the Origin header.
- Inherit ServerEndpointConfig.Configurator and override the checkOrigin method:
public class CustomConfigurator extends ServerEndpointConfig.Configurator {
private static final String ORIGIN = "https://www.trust1.com";
@Override
public boolean checkOrigin(String originHeaderValue) {
if(null==originHeaderValue || originHeaderValue.trim().length()==0)
return false;
return ORIGIN.equals(originHeaderValue);
}
}
Or when the whitelist is a list:
private static final List<String> ORIGIN_LIST = Arrays.asList(“http://m.trust1.com”,“http://test-s.trust1.com”,“https://s.trust1.com”);
if(!ORIGIN_LIST.contains(req.headers().get(“Origin”))) {
return false
}
- Use custom configuration:
@ServerEndpoint(value="/echo",configurator = CustomConfigurator.class)
public class EchoEndpoint {
//do something
}
Logic loopholes
Participant Judgment
Positive solution coding method:
- Into the judgement of positive and negative, amount, quantity, etc. related
if(request.getCoupons() <= 0){
throw new Exception();
}
- Input parameter value range
- Gift id, draw type, age, mobile phone number, etc.
- Timely offline expired activity type, gift id
long expireTime = getExpireTime(productId);
Boolean isExpire = checkExpire(expireTime);
- Participant combination judgment, for example, type a activities should get gift 1, but cannot get gift 2 of type b activities
String type = request.getType();
String productId = request.getProductId();
if(null==type || null==productId){
//handle error
}
if('a'.equals(type){
if(!'1'.equals(productId)){
throw new Exception();
}
} else if('b'.equals(type){
if(!'2'.equals(productId)){
throw new Exception();
}
} else
{
//handle error
}
- Signature verification, adding sign to the input parameters to verify the source of the request, while preventing the request parameters from being tampered with
public static String checkSign(String appId, Object... args) {
//线下约定appId
String appSecret = getAppsecret(appId);
if(null==appSecret){
//handle error
}
return DigestUtils.sha256Hex(appSecret + “|” + Joiner.on("|").join(args));
}
- Three-party payment loopholes, for example: limited discount purchases, guaranteed to generate only one order
//1、加锁
Lock(id+productId);
try {
lock.acquire();
//2、判断是否已有定单
if(Exist(id+productId))){
//3、如果定单成功,返回已购买过;如果定单失败,返回请支付
…
}
return response;
} finally
lock.release();
}
Or judge in the callback of the three-party payment (the former method is recommended)
//1、加锁
Lock(id+productId);
try {
lock.acquire();
//2、判断是否已支付
if(Payed(id+productId)){
//3、如果购买过且支付成功,退款
…
}
return response;
} finally
lock.release();
}
Integer overflow
Positive solution coding method:
- Type conversion should verify the data type and data range:
public static boolean isValid(String str) {
if(null==str){
return false;
}
if (str.length() > 8 || str.length() <= 0) {
return false;
}
char[] chars = str.toCharArray();
for (int i = 0; i < chars.length; i++) {
if (!Character.isDigit(chars[i])) {
return false;
}
}
return true;
}
- If transaction data is involved, the actual meaning of the data should also be considered. For example, the number of orders, payment amount, etc. are positive numbers. For the numerical calculations involved, subtraction and division should be used instead of addition and multiplication:
public static boolean checkValue(int number,int increase) {
final int total=100000;
if(number<0 || increase<0 || number>total-increase){
return false;
}
return true;
}
- Addition and multiplication, you can also use the java.lang.Math method in jdk, Math.addExact and Math.multiplyExact, these two functions will throw an exception when overflow
try {
int ret = Math.addExact(number, increase);
if(ret > total){
return false;
}
return true;
}catch (Exception e){
return false;
}
- When using constants, pay attention to the position of L
aa = 2147483647*1000*100L;//有溢出
aa = 2147483647L*1000*100;//无溢出
Resource not released
Positive solution coding method:
Such security issues should ensure that any execution path releases resources:
try {
Statement stmt = conn.createStatement();
ResultSet rs=stmt.executeQuery(sqlBase);
//do something
} catch (Exception e) {
//do something
}
finally{
stmt.close();
}
Ultra vires
Positive solution coding method:
The data attribution should be judged:
@RequestMapping(value="/delete/{addrId}")
public Object remove(@PathVariable Long addrId){
Map<String, Object> respMap = new HashMap<String, Object>();
if (WebUtils.isLogged()) {
this.addressService.removeUserAddress(addrId,WebUtils.getLoggedUserId()); //关联用户身份
respMap.put(Constants.RESP_STATUS_CODE_KEY, Constants.RESP_STATUS_CODE_SUCCESS);
respMap.put(Constants.MESSAGE,"地址删除成功!");
}
Concurrency issues
Positive solution coding method:
- Use mysql transaction, under the premise of using transaction, you should use pessimistic lock or optimistic lock to solve:
Pessimistic lock: use select for update and pessimistic lock in the transaction to ensure that the latest data is always obtained
String sql_select="select num from oversold where id=1 for update";
String sql_update="update oversold set num=? where id =1";
conn.setAutoCommit(false);
try {
PreparedStatement pre_select=conn.prepareStatement(sql_select);
PreparedStatement pre_update=conn.prepareStatement(sql_update);
ResultSet res=pre_select.executeQuery();
if (res.next()) {
int num=Integer.parseInt(res.getString(1));
num--;
if(num>0){
//do something
pre_update.setInt(1, num);
pre_update.executeUpdate();
}
}
conn.commit();
} catch (Exception e) {
conn.rollback();
}
Optimistic lock: A new field version needs to be used to save the version number:
String sql_select="select num,version from oversold where id=1";
String sql_update="update oversold set num=num-1,version=version+1 where id =1 and version=?";
conn.setAutoCommit(false);
try {
PreparedStatement pre_select=conn.prepareStatement(sql_select);
PreparedStatement pre_update=conn.prepareStatement(sql_update);
ResultSet res=pre_select.executeQuery();
if (res.next()) {
int num=Integer.parseInt(res.getString("num"));
int version=Integer.parseInt(res.getString("version"));
TimeUnit.SECONDS.sleep(10);
if(num>0){
//do something
pre_update.setInt(1, version);
int ret = pre_update.executeUpdate();
if(ret <= 0){
//update失败,此时version可能已过期
}
}
}
conn.commit();
} catch (Exception e) {
conn.rollback();
}
Pessimistic locking will bring a relatively large performance overhead, while optimistic locking will read dirty data. The specific locking method used can be determined according to specific business scenarios.
- Through redis lock, the company's redis version has been upgraded to 2.6.12 or higher, so use set instead of inc(setnx)/expire, which also guarantees atomicity
jedis.set(String key, String value, String nxxx, String expx, int time)
- Use distributed locks (for example: use InterProcessMutex)
DistributedLock lock = new DistributedLock(“****”, id;
try {
lock.acquire();
//比较:判断是否已经支付、领取等
//修改:支付、修改领取数量
return response;
} finally {
lock.release();
}
Sensitive information
Positive solution coding method:
The web.xml file should be configured to handle exceptions globally:
<error-page>
<error-code>404</error-code>
<location>/error.jsp</location>
<error-code>500</error-code>
<location>/error.jsp</location>
</error-page>
Sensitive front-end information:
1) Comments should be deleted before the code goes online (especially sensitive information in js scripts, html pages, account passwords, mobile phone numbers, special links, etc.)
2) The parameters returned by the web interface to the front-end, if there are sensitive information, should be coded or encrypted, as shown below:
1、身份证
1****************4
2、手机号
13*******34
3、姓名(注意所有接口保持一致,不要有的接口返回是姓*,有的接口返回是*名,这样还是会导致信息泄漏)
姓*
4、地理位置
小数点后三位,(39.910, 116.397)
5、IP
不要返回ip
To encrypt:
- When business needs to be displayed: parameter content is encrypted with aes-256
- No need to show: parameter content uses hash algorithm sha-256