[Transfer] Momo Java Security Coding Specification

Momo Java Security Coding Specification
https://github.com/momosecurity/rhizobia_J

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:

  1. 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);
  1. 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

  1. 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 ;
  1. 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:

  1. 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.

  1. 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()
  1. 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";
}
  1. 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:

  1. 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:

  1. 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);
  1. 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:

  1. 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);
    }
}
  1. 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.

  1. 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
}
  1. Use custom configuration:
@ServerEndpoint(value="/echo",configurator = CustomConfigurator.class)
public class EchoEndpoint {
    //do something
}

Logic loopholes

Participant Judgment

Positive solution coding method:

  1. Into the judgement of positive and negative, amount, quantity, etc. related
if(request.getCoupons() <= 0){
  throw new Exception();
  }
  1. 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);
  1. 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
}
  1. 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));
}
  1. 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:

  1. 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;
}
  1. 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;
}
  1. 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;
    }
  1. 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:

  1. 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.

  1. 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)
  1. 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

Guess you like

Origin blog.csdn.net/weixin_43438052/article/details/114063465