[Transferir] Especificación de codificación de seguridad de Momo Java

Especificación de codificación de seguridad de Momo Java
https://github.com/momosecurity/rhizobia_J

Especificación de codificación segura de JAVA

1. Principios básicos de la codificación segura

1.1 Todos los datos de entrada son dañinos

Entrada de datos directa:
Para la entrada de datos por parte del usuario a través de GET, POST, COOKIE, REQUEST, etc. y la fuente de datos que proporciona el framework, es decir, todas las variables transmitidas desde el cliente en el protocolo de comunicación, ya sea el datos rellenados manualmente por el usuario o el cliente navegación Los datos rellenados automáticamente por el dispositivo o el sistema operativo pueden causar problemas de seguridad y requerir controles de seguridad estrictos.

Datos de entrada indirectos: datos
obtenidos de bases de datos, archivos, redes, API internas, etc., es decir, algunos datos que no se derivan directamente de los usuarios, pero que no son datos constantes definidos en el programa. Por ejemplo, la entrada del usuario se convierte y se envía a una base de datos o archivo tras capa por capa, y cuando se reutiliza más tarde, los datos obtenidos en este momento aún no son confiables y también se requieren controles de seguridad estrictos.

1.2 Configuración de seguridad que no depende del entorno operativo

No puede esperar las opciones de seguridad del archivo de configuración, debe poner el programa en la configuración más insegura para su consideración.

1.3 Las medidas de control de seguridad se implementan en la etapa de implementación final

Cada problema de seguridad tiene sus propias razones, por ejemplo, la razón de la inyección SQL es el empalme de los parámetros de la declaración SQL. Por lo tanto, para evitar problemas de inyección de SQL, es necesario manejar de forma segura los parámetros antes de que se ejecute la instrucción SQL, porque en este momento, se puede determinar el tipo de datos del parámetro esperado, rango de datos, etc.

1.4 Minimización

El principio de minimización se aplica a todos los campos relacionados con la seguridad El principal rendimiento en la seguridad del código es:
1. Minimizar la entrada del usuario. Utilice la menor cantidad de información posible por parte del usuario.
2. Se minimiza el rango de entrada del usuario. La estrategia de lista blanca debe usarse al filtrar parámetros, y la validez de los parámetros se puede verificar para los parámetros que se pueden definir claramente, como correo electrónico, número de tarjeta, número de identificación, etc.
3. Minimice la información de devolución. La información de error del programa debe protegerse del usuario y la información de error original no debe devolverse directamente al lado del usuario.

1.5 Terminación fallida

Al realizar verificaciones de seguridad sobre los datos enviados por el usuario, si los datos no cumplen con los requisitos, la ejecución del negocio debe darse por terminada, y no intente modificar y convertir los parámetros enviados por el usuario para continuar con la ejecución.

2. Métodos de codificación de seguridad correspondientes a vulnerabilidades comunes

Inyección de comando

Método de codificación de solución positiva:

  1. Coincidir exactamente con los datos enviados por el usuario
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. Usar lista blanca
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

Inyección de código

Método de codificación de solución positiva:

Se debe utilizar la lista blanca:

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;
}

inyección SQL

Método de codificación de solución positiva:

Se debe utilizar una consulta parametrizada

a. Método de consulta parametrizado:

jdbc

Se debe utilizar PreparedStatement:

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

Se debe utilizar el texto "#":

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

Nota:

Independientemente de si el proyecto usa el marco, los parámetros de usuario deben usar el nombre de la tabla, el nombre del campo o involucrar el orden por, agrupar por, limitar las operaciones, la consulta parametrizada hará que el nombre de la tabla, el nombre del campo pierda su significado original

  1. Usar métodos en el SDK de seguridad de Java
    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. Utilice el procesamiento de la lista blanca:
switch (columnName){
    case "name":columnName="name";
        break;
    case "num":columnName="num";
        break;
    default:columnName="id";  
}

Inyección de Mongo

Método de codificación de solución positiva:

No se pueden empalmar parámetros directamente, 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();
}

XX

Método de codificación de solución positiva:

  1. Al analizar datos XML, el análisis de los parámetros DTD (doctypes) debe restringirse:
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
}

Inyección Xpath

Método de codificación de solución positiva:

Consultas que deben parametrizarse mediante 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

Método de codificación de solución positiva:

Debería emitirse después de la codificación. Cuando se envía a diferentes etiquetas html o atributos en la interfaz, se utilizan diferentes métodos de codificación.

  1. Al usar 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. Al usar el marco de Spring, puede usar el código HtmlUtils.htmlEscape que viene con el marco para generar en entidades html:
@RequestMapping("/xsstest")
public String xssTest(@RequestParam("id") String id, Model model){
  
    id=HtmlUtils.htmlEscape(id);
    model.addAttribute("id",id);
    return "index";
}
  1. Cuando no se usa el marco, la codificación StringEscapeUtils.escapeHtml de la biblioteca commons-lang se puede usar para generar en la entidad html:
<%
   String id=request.getParameter("id");
   out.println(StringEscapeUtils.escapeHtml(id));
%>

CSRF

Método de codificación de solución positiva:

  1. Una vez autenticado el pasaporte web, se implantará csrf_token en la cookie. Para tales problemas de seguridad, el front-end debe obtener el csrf_token de la cookie y enviar la solicitud que contiene el valor csrf_token por POST. El código es el siguiente:
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
        }
    }
});

El backend debe extraer el valor del parámetro csrf_token del cuerpo de la solicitud POST para su verificación. El código es el siguiente:

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;
}

El método getCSRFTokenBySession se implementa de la siguiente manera:

public String getCSRFTokenBySession(String session){
        return md5(session);
    }

Vulnerabilidad de redireccionamiento de URL

Método de codificación de solución positiva:

El servidor debe evitar redireccionamientos inseguros y saltos de acuerdo con los requisitos comerciales específicos:

  1. Si solo desea saltar al dominio actual, o los enlaces después del salto son relativamente pocos y relativamente fijos, entonces los parámetros deben incluirse en la lista blanca en el lado del servidor, y se prohíbe la redirección de las URL que no están en la lista blanca;
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. Si debido a necesidades comerciales, los enlaces después del salto a menudo cambian y hay más, debe crear una página de salto intermedia para recordar a los usuarios que irán a otros sitios web y, por favor, preste atención a la prevención de ataques de phishing.

SSRF

Método de codificación de solución positiva:

Tales problemas de seguridad deben limitar la URL solicitada en el lado del servidor: el lado del servidor mantiene una relación de mapeo para una lista de solicitudes de recursos, y el lado del servidor obtiene el recurso solicitado real de la relación de mapeo de acuerdo con los parámetros de solicitud enviados por el cliente. Al mismo tiempo, deben prohibirse las solicitudes de segmentos de direcciones privados y nombres de dominio de intranet.

Recorrido arbitrario de archivos

Método de codificación de solución positiva:

La lista blanca debe usarse para controlar la ruta:

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
}

Subir archivo

Método de codificación de solución positiva:

  • Verificar el tamaño del archivo de carga
  • Si el tipo de archivo cumple con los requisitos
  • El nombre del archivo original en el parámetro no se puede usar directamente, el nombre del archivo debe generarse aleatoriamente y el sufijo debe ser limitado
  • Guardar en el servidor de archivos
    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";
    }

Vulnerabilidad de deserialización

Método de codificación de solución positiva:

  1. Para tales problemas, debe anular el método resolveClass de la clase ObjectInputStream para verificar el nombre de clase del objeto que se deserializará:
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 y jackson también tienen problemas con la deserialización, y deben usarse las siguientes versiones:
  • fastjson, versión 1.2.46 y superior
  • jackson, 2.9.8 y superior

Secuestro de WebSocket

Método de codificación de solución positiva:

El servidor debe verificar el encabezado Origin.

  1. Herede ServerEndpointConfig.Configurator y anule el método checkOrigin:
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);
    }
}

O cuando la lista blanca es una lista:

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. Usar configuración personalizada:
@ServerEndpoint(value="/echo",configurator = CustomConfigurator.class)
public class EchoEndpoint {
    //do something
}

Lagunas lógicas

Juicio del participante

Método de codificación de solución positiva:

  1. En el juicio de positivo y negativo, cantidad, cantidad, etc.relacionado
if(request.getCoupons() <= 0){
  throw new Exception();
  }
  1. Rango de valor de parámetro de entrada
  • Identificación de regalo, tipo de sorteo, edad, número de teléfono móvil, etc.
  • Tipo de actividad caducada fuera de línea oportuna, ID de regalo
long expireTime = getExpireTime(productId);
Boolean isExpire = checkExpire(expireTime);
  1. Juicio de combinación del participante, por ejemplo, las actividades de tipo a deben recibir el regalo 1, pero no pueden recibir el regalo 2 de las actividades de tipo b
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. Verificación de la firma, agregando signo a los parámetros de entrada para verificar el origen de la solicitud, al tiempo que evita que los parámetros de la solicitud sean manipulados
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. Lagunas de pago de tres partes, por ejemplo: compras con descuento limitado, garantizadas para generar solo un pedido
//1、加锁
Lock(id+productId);
try {
    lock.acquire();
    //2、判断是否已有定单
    if(Exist(id+productId))){
        //3、如果定单成功,返回已购买过;如果定单失败,返回请支付
        …
    }
    return response;
} finally
    lock.release();
}

O juzgue en la devolución de llamada del pago tripartito (se recomienda el método anterior)

//1、加锁
Lock(id+productId);
try {
    lock.acquire();
    //2、判断是否已支付
    if(Payed(id+productId)){
        //3、如果购买过且支付成功,退款
        …
    }
    return response;
} finally
    lock.release();
}

Desbordamiento de enteros

Método de codificación de solución positiva:

  1. La conversión de tipo debe verificar el tipo de datos y el rango de datos:
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. Si se trata de datos de transacciones, también se debe considerar el significado real de los datos. Por ejemplo, el número de pedidos, el monto del pago, etc. son números positivos, y el cálculo numérico involucrado debe usar resta y división en lugar de suma y multiplicación:
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. Suma y multiplicación, también puede usar el método java.lang.Math en jdk, Math.addExact y Math.multiplyExact, estas dos funciones arrojarán una excepción cuando se desborde
    try {
        int ret = Math.addExact(number, increase);
        if(ret > total){
            return false;
        }
        return true;
    }catch (Exception e){
        return false;
    }
  1. Cuando use constantes, preste atención a la posición de L
aa = 2147483647*1000*100L;//有溢出
aa = 2147483647L*1000*100;//无溢出

Recurso no liberado

Método de codificación de solución positiva:

Estos problemas de seguridad deben garantizar que cualquier ruta de ejecución libere recursos:

try {
     Statement stmt = conn.createStatement();
     ResultSet rs=stmt.executeQuery(sqlBase);     
     //do something         
} catch (Exception e) {
     //do something
}
finally{
     stmt.close();
}

Ultra vires

Método de codificación de solución positiva:

La atribución de datos debe juzgarse:

@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,"地址删除成功!");
    }

Problemas de concurrencia

Método de codificación de solución positiva:

  1. Use la transacción mysql, bajo la premisa de usar la transacción, debe usar un bloqueo pesimista o un bloqueo optimista para resolver:

Bloqueo pesimista: use select para actualizar y bloqueo pesimista en la transacción para asegurarse de que siempre se obtengan los datos más recientes

    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();
    }

Bloqueo optimista: es necesario utilizar una nueva versión de campo para guardar el número de versión:

    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();
    }

El bloqueo pesimista traerá una sobrecarga de rendimiento relativamente grande, mientras que el bloqueo optimista leerá datos sucios. El método de bloqueo específico utilizado se puede determinar de acuerdo con escenarios comerciales específicos.

  1. A través del bloqueo de redis, la versión de redis de la compañía se ha actualizado a 2.6.12 o superior, así que use set en lugar de inc (setnx) / expire, que también garantiza la atomicidad
jedis.set(String key, String value, String nxxx, String expx, int time)
  1. Use bloqueos distribuidos (por ejemplo: use InterProcessMutex)
DistributedLock lock = new DistributedLock(“****”, id;
try {
        lock.acquire();
        //比较:判断是否已经支付、领取等
        //修改:支付、修改领取数量
        return response;
    } finally {
        lock.release();
    }

Información sensible

Método de codificación de solución positiva:

El archivo web.xml debe configurarse para manejar excepciones globalmente:

<error-page>
    <error-code>404</error-code>
    <location>/error.jsp</location> 
    <error-code>500</error-code>
    <location>/error.jsp</location> 
</error-page>

Información sensible del front-end:

1) Los comentarios deben eliminarse antes de que el código esté en línea (especialmente información confidencial en scripts js, páginas html, contraseñas de cuentas, números de teléfonos móviles, enlaces especiales, etc.)

2) Los parámetros devueltos por la interfaz web al front-end, si hay información confidencial, deben estar codificados o encriptados, como se muestra a continuación:

    1、身份证
        1****************4
    2、手机号
        13*******34
    3、姓名(注意所有接口保持一致,不要有的接口返回是姓*,有的接口返回是*名,这样还是会导致信息泄漏)
        姓*
    4、地理位置
        小数点后三位,(39.910, 116.397)
    5、IP
        不要返回ip

Para cifrar:

  • Cuando es necesario mostrar el negocio: el contenido de los parámetros está encriptado con aes-256
  • No es necesario mostrar: el contenido del parámetro utiliza el algoritmo hash sha-256

Supongo que te gusta

Origin blog.csdn.net/weixin_43438052/article/details/114063465
Recomendado
Clasificación