[Transfert] Spécification du codage de sécurité Momo Java

Spécification de codage de sécurité Momo Java
https://github.com/momosecurity/rhizobia_J

Spécification de codage sécurisé JAVA

1. Principes de base du codage sécurisé

1.1 Toutes les données d'entrée sont nuisibles

Saisie directe des données:
Pour la saisie de données par l'utilisateur via GET, POST, COOKIE, REQUEST, etc. et la source de données fournie par le framework, c'est-à-dire toutes les variables transmises par le client dans le protocole de communication, que ce soit le Données renseignées manuellement par l'utilisateur ou le client naviguant Les données renseignées automatiquement par l'appareil ou le système d'exploitation peuvent poser des problèmes de sécurité et nécessiter des contrôles de sécurité stricts.

Données d'entrée indirectes: données
obtenues à partir de bases de données, fichiers, réseaux, API internes, etc., c'est-à-dire certaines données qui ne sont pas directement dérivées des utilisateurs, mais qui ne sont pas des données constantes définies dans le programme. Par exemple, l'entrée de l'utilisateur est convertie et sortie dans une base de données ou un fichier après couche par couche, et lorsqu'elle est réutilisée ultérieurement, les données obtenues à ce moment ne sont toujours pas dignes de confiance et des contrôles de sécurité stricts sont également nécessaires.

1.2 Configuration de sécurité qui ne dépend pas de l'environnement d'exploitation

Vous ne pouvez pas vous attendre aux options de sécurité du fichier de configuration, vous devez mettre le programme dans la configuration la plus non sécurisée pour examen.

1.3 Les mesures de contrôle de sécurité sont mises en œuvre lors de la phase finale de mise en œuvre

Chaque problème de sécurité a ses propres raisons. Par exemple, la raison de l'injection SQL est l'épissage des paramètres des instructions SQL. Par conséquent, pour éviter les problèmes d'injection SQL, il est nécessaire de gérer en toute sécurité les paramètres avant l'exécution de l'instruction SQL, car à ce stade, le type de données de paramètre attendu, la plage de données, etc. peuvent être déterminés.

1.4 Minimisation

Le principe de minimisation s'applique à tous les champs liés à la sécurité Les principales performances en matière de sécurité du code sont les suivantes:
1. Minimiser les entrées utilisateur. Utilisez le moins d'entrée possible de l'utilisateur.
2. La plage d'entrée utilisateur est minimisée. La stratégie de liste blanche doit être utilisée lors du filtrage des paramètres, et la validité des paramètres peut être vérifiée pour les paramètres qui peuvent être clairement définis, tels que l'e-mail, le numéro de carte, le numéro d'identification, etc.
3. Minimisez les informations de retour. Les informations d'erreur de programme doivent être protégées de l'utilisateur et les informations d'erreur d'origine ne doivent pas être directement renvoyées au côté utilisateur.

1.5 Échec de la résiliation

Lorsque vous effectuez des contrôles de sécurité sur les données soumises par l'utilisateur, si les données ne répondent pas aux exigences, l'exécution de l'entreprise doit être interrompue et n'essayez pas de modifier et de convertir les paramètres soumis par l'utilisateur pour poursuivre l'exécution.

2. Méthodes de codage de sécurité correspondant aux vulnérabilités courantes

Injection de commande

Méthode de codage de la solution positive:

  1. Correspond exactement aux données soumises par l'utilisateur
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. Utiliser la liste blanche
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

Injection de code

Méthode de codage de la solution positive:

La liste blanche doit être utilisée:

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

Injection SQL

Méthode de codage de la solution positive:

Une requête paramétrée doit être utilisée

a. Méthode de requête paramétrée:

jdbc

PreparedStatement doit être utilisé:

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

Le libellé de "#" doit être utilisé:

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

Remarque:

Indépendamment du fait que le projet utilise le cadre, les paramètres utilisateur doivent utiliser le nom de la table, le nom du champ ou impliquer les opérations de tri, de regroupement, de limite, la requête paramétrée fera perdre au nom de la table, au nom du champ sa signification d'origine

  1. Utiliser des méthodes dans le SDK de sécurité 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. Utilisez le traitement de la liste blanche:
switch (columnName){
    case "name":columnName="name";
        break;
    case "num":columnName="num";
        break;
    default:columnName="id";  
}

Injection de Mongo

Méthode de codage de la solution positive:

Impossible d'épisser directement les paramètres, utilisez 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();
}

XXE

Méthode de codage de la solution positive:

  1. Lors de l'analyse des données XML, l'analyse des paramètres DTD (doctypes) doit être restreinte:
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
}

Injection Xpath

Méthode de codage de la solution positive:

Requêtes qui doivent être paramétrées à l'aide de 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éthode de codage de la solution positive:

Lors de la sortie vers différentes balises html ou attributs sur le front-end, différentes méthodes de codage sont utilisées.

  1. Lors de l'utilisation d'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. Lorsque vous utilisez le framework Spring, vous pouvez utiliser le code HtmlUtils.htmlEscape fourni avec le framework pour générer des entités html:
@RequestMapping("/xsstest")
public String xssTest(@RequestParam("id") String id, Model model){
  
    id=HtmlUtils.htmlEscape(id);
    model.addAttribute("id",id);
    return "index";
}
  1. Lorsque le framework n'est pas utilisé, l'encodage StringEscapeUtils.escapeHtml de la bibliothèque commons-lang peut être utilisé pour sortir vers l'entité html:
<%
   String id=request.getParameter("id");
   out.println(StringEscapeUtils.escapeHtml(id));
%>

CSRF

Méthode de codage de la solution positive:

  1. Une fois le passeport Web authentifié, csrf_token sera implanté dans le cookie. Pour de tels problèmes de sécurité, le frontal doit obtenir le csrf_token du cookie et soumettre la requête contenant la valeur csrf_token par POST. Le code est le suivant:
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
        }
    }
});

Le backend doit extraire la valeur du paramètre csrf_token du corps de la requête POST pour vérification. Le code est le suivant:

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

La méthode getCSRFTokenBySession est implémentée comme suit:

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

Vulnérabilité de redirection d'URL

Méthode de codage de la solution positive:

Le serveur doit empêcher les redirections et les sauts non sécurisés en fonction des exigences métier spécifiques:

  1. Si vous voulez seulement sauter dans le domaine actuel, ou si les liens après le saut sont relativement peu nombreux et relativement fixes, alors les paramètres doivent être mis en liste blanche côté serveur et les URL dans la liste non blanche ne peuvent pas être redirigées;
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 en raison des besoins de l'entreprise, les liens après le saut changent souvent et qu'il y en a d'autres, vous devez créer une page de renvoi intermédiaire pour rappeler aux utilisateurs qu'ils vont accéder à d'autres sites Web, et faites attention à empêcher les attaques de phishing.

SSRF

Méthode de codage de la solution positive:

De tels problèmes de sécurité devraient limiter l'URL demandée côté serveur: le côté serveur maintient une relation de mappage pour une liste de demandes de ressources, et le côté serveur obtient la ressource demandée réelle à partir de la relation de mappage en fonction des paramètres de demande soumis par le client. Dans le même temps, les demandes de segments d'adresses privées et de noms de domaine intranet devraient être interdites.

Traversée arbitraire de fichiers

Méthode de codage de la solution positive:

La liste blanche doit être utilisée pour contrôler le chemin:

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
}

Téléchargement de fichiers

Méthode de codage de la solution positive:

  • Vérifier la taille du fichier de téléchargement
  • Si le type de fichier répond aux exigences
  • Le nom de fichier d'origine dans le paramètre ne peut pas être utilisé directement, le nom de fichier doit être généré aléatoirement et le suffixe doit être limité
  • Enregistrer sur le serveur de fichiers
    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";
    }

Vulnérabilité de désérialisation

Méthode de codage de la solution positive:

  1. Pour de tels problèmes, vous devez remplacer la méthode resolClass de la classe ObjectInputStream pour vérifier le nom de classe de l'objet à désérialiser:
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 et jackson ont également des problèmes de désérialisation, et les versions suivantes doivent être utilisées:
  • fastjson, version 1.2.46 et supérieure
  • jackson, 2.9.8 et plus

Détournement de WebSocket

Méthode de codage de la solution positive:

Le serveur doit vérifier l'en-tête Origin.

  1. Héritez ServerEndpointConfig.Configurator et remplacez la méthode 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);
    }
}

Ou lorsque la liste blanche est une liste:

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. Utilisez une configuration personnalisée:
@ServerEndpoint(value="/echo",configurator = CustomConfigurator.class)
public class EchoEndpoint {
    //do something
}

Lacunes logiques

Jugement du participant

Méthode de codage de la solution positive:

  1. Dans le jugement de positif et négatif, montant, quantité, etc. liés
if(request.getCoupons() <= 0){
  throw new Exception();
  }
  1. Plage de valeurs des paramètres d'entrée
  • Identifiant du cadeau, type de tirage, âge, numéro de téléphone portable, etc.
  • Type d'activité expirée hors connexion en temps opportun, identifiant du cadeau
long expireTime = getExpireTime(productId);
Boolean isExpire = checkExpire(expireTime);
  1. Le jugement de combinaison des participants, par exemple, les activités de type a devraient recevoir le cadeau 1, mais ne peuvent pas obtenir le cadeau 2 des activités de type 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. Vérification de la signature, ajout d'un signe aux paramètres d'entrée pour vérifier la source de la demande, tout en empêchant les paramètres de la demande d'être falsifiés
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. Des failles de paiement à trois parties, par exemple: des achats à rabais limités, garantissant de ne générer qu'une seule commande
//1、加锁
Lock(id+productId);
try {
    lock.acquire();
    //2、判断是否已有定单
    if(Exist(id+productId))){
        //3、如果定单成功,返回已购买过;如果定单失败,返回请支付
        …
    }
    return response;
} finally
    lock.release();
}

Ou juger dans le rappel du paiement tripartite (l'ancienne méthode est recommandée)

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

Débordement d'entier

Méthode de codage de la solution positive:

  1. La conversion de type doit vérifier le type de données et la plage de données:
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 des données de mouvement sont impliquées, la signification réelle des données doit également être prise en compte. Par exemple, le nombre de commandes, le montant du règlement, etc. sont des nombres positifs et le calcul numérique impliqué doit utiliser la soustraction et la division au lieu de l'addition et de la 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 et multiplication, vous pouvez également utiliser la méthode java.lang.Math dans jdk, Math.addExact et Math.multiplyExact, ces deux fonctions lèveront une exception en cas de débordement
    try {
        int ret = Math.addExact(number, increase);
        if(ret > total){
            return false;
        }
        return true;
    }catch (Exception e){
        return false;
    }
  1. Lorsque vous utilisez des constantes, faites attention à la position de L
aa = 2147483647*1000*100L;//有溢出
aa = 2147483647L*1000*100;//无溢出

Ressource non libérée

Méthode de codage de la solution positive:

Ces problèmes de sécurité doivent garantir que tout chemin d'exécution libère des ressources:

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

Ultra vires

Méthode de codage de la solution positive:

L'attribution des données doit être jugée:

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

Problèmes de concurrence

Méthode de codage de la solution positive:

  1. Utilisez mysql transaction, dans le cadre de l'utilisation de transaction, vous devez utiliser un verrou pessimiste ou un verrou optimiste pour résoudre:

Verrou pessimiste: utilisez select pour la mise à jour et verrou pessimiste dans la transaction pour vous assurer que les dernières données sont toujours obtenues

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

Verrou optimiste: une nouvelle version de champ doit être utilisée pour enregistrer le numéro de version:

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

Le verrouillage pessimiste entraînera une surcharge de performances relativement importante, tandis que le verrouillage optimiste lira les données modifiées. La méthode de verrouillage spécifique utilisée peut être déterminée en fonction de scénarios commerciaux spécifiques.

  1. Grâce à redis lock, la version redis de la société a été mise à niveau vers la version 2.6.12 ou supérieure, utilisez donc set au lieu de inc (setnx) / expire, ce qui garantit également l'atomicité.
jedis.set(String key, String value, String nxxx, String expx, int time)
  1. Utilisez des verrous distribués (par exemple: utilisez InterProcessMutex)
DistributedLock lock = new DistributedLock(“****”, id;
try {
        lock.acquire();
        //比较:判断是否已经支付、领取等
        //修改:支付、修改领取数量
        return response;
    } finally {
        lock.release();
    }

Information sensible

Méthode de codage de la solution positive:

Le fichier web.xml doit être configuré pour gérer les exceptions globalement:

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

Informations frontales sensibles:

1) Les commentaires doivent être supprimés avant la mise en ligne du code (en particulier les informations sensibles dans les scripts js, les pages html, les mots de passe de compte, les numéros de téléphone portable, les liens spéciaux, etc.)

2) Les paramètres renvoyés par l'interface Web au front-end, s'il y a des informations sensibles, doivent être codés ou chiffrés, comme indiqué ci-dessous:

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

Pour crypter:

  • Lorsque l'entreprise doit être affichée: le contenu des paramètres est chiffré avec AES-256
  • Pas besoin de montrer: le contenu du paramètre utilise l'algorithme de hachage sha-256

Je suppose que tu aimes

Origine blog.csdn.net/weixin_43438052/article/details/114063465
conseillé
Classement