Skip to content

Commit

Permalink
Merge pull request #89 from adamjsturge/dev
Browse files Browse the repository at this point in the history
Dev with bigger body size and testing
  • Loading branch information
adamjsturge authored Jul 25, 2024
2 parents 6a8a7b2 + 6dff361 commit 06e0d4c
Show file tree
Hide file tree
Showing 12 changed files with 134 additions and 53 deletions.
27 changes: 26 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,39 @@ Look at the current issues and see if there is anything you want to do. I'm tryi

## Dev Environment

Start by forking the repo!

Actually super simple, we have a .vscode with recommended extensions and settings to make sure the code passes linter and scans.
Copy the .env.copy to .env then chaning the values to make sure it works for you. I notice that everychange you have to run
```bash
docker compose build && docker compose up
git clone [email protected]:yourgithubusernamefork/xsshunter-go.git
cd xsshunter-go
cp .env.copy .env
docker compose up --build
```

Luckily go is fast and it shouldn't take too long for it to build

## Git Workflow

Please make a new branch for every feature or bug fix you are working on. This makes it easier to review and merge your code. We also merge into branch `dev` and then `main` is merged from `dev` when we are ready to release. So please make sure your PR is against `dev`.

## Code Style

I'm using the golangci-lint to make sure the code is formatted correctly. If you are using vscode you can install the extension and it will format the code for you. If you are not using vscode you can run `golangci-lint run` to make sure the code is formatted correctly.

## Commit Messages

Please make sure your commit messages are clear and concise. If you are fixing a bug please include the issue number in the commit message. If you are adding a new feature please include a brief description of the feature.

## Pull Requests

Please make sure your PR is against the `dev` branch. Please make sure your PR is clear and concise. If you are fixing a bug please include the issue number in the PR. If you are adding a new feature please include a brief description of the feature.

## Testing

I'm trying to add tests to everything I can. If you are adding a new feature please add tests to it. If you are fixing a bug please add a test to make sure it doesn't happen again. All tests are in the e2e folder and are run with playwright.

## Your own fork

Adding `DOCKER_USERNAME` and `DOCKER_PASSWORD`([DOCKER TOKEN](https://hub.docker.com/settings/security)) to your forked secrets will allow the pipeline to automatically push to docker and you can have your own docker image.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ XSSHunter-go is a self-hosted XSS hunter that allows you to create a custom XSS
<!-- Table of content -->
<summary>Table of content</summary>
<ol>
<li><a href="#setup-dev-environment">Setup Dev Environment</a></li>
<li><a href="#setup">Setup</a></li>
<li><a href="#volumes">Volumes</a></li>
<li><a href="#notifications">Notifications</a></li>
<li><a href="#using-traefik-for-ssl">Using Traefik for SSL</a></li>
<li><a href=>
</ol>

The idea of why I decided to code this in Go is because I wanted this to be a maintained project that is stable. The original is based of Node 12 which is end of life. I also wanted to add some features that I thought would be useful (mostly expanding the notification system).
Expand Down Expand Up @@ -112,3 +112,11 @@ services:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./shared:/shared"
```
## Contributing
Thanks for looking to contribute.
If you have a massive feature change I recommend putting as an issue before starting anything! That way we can discuss it before you start working on it.
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for more information on how to contribute and everything you need to know.
2 changes: 1 addition & 1 deletion api.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ func recordInjectionHandler(w http.ResponseWriter, r *http.Request) {

err := r.ParseMultipartForm(32 << 20)
if err != nil {
log.Fatal(err)
log.Fatal("Fatal Error Parse Multiform:", err)
}
owner_correlation_key := r.FormValue("owner_correlation_key")
if owner_correlation_key == "" {
Expand Down
6 changes: 3 additions & 3 deletions const.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func get_pages_to_collect() string {
func set_pages_to_collect() {
pages_to_collect_value, err := db_single_item_query("SELECT value FROM settings WHERE key = $1", PAGES_TO_COLLECT_SETTINGS_KEY).toString()
if err != nil {
log.Fatal(err)
log.Fatal("Fatal Error on set page collect:", err)
}

pages_to_collect = "[" + pages_to_collect_value + "]"
Expand All @@ -52,7 +52,7 @@ func get_chainload_uri() string {
func set_chainload_uri() {
chainload_uris_value, err := db_single_item_query("SELECT value FROM settings WHERE key = $1", CHAINLOAD_URI_SETTINGS_KEY).toString()
if err != nil {
log.Fatal(err)
log.Fatal("Fatal Error on set chainload uri:", err)
}
chainload_uris = chainload_uris_value
}
Expand All @@ -64,7 +64,7 @@ func get_send_alerts() bool {
func set_send_alerts() {
send_alerts_value, err := db_single_item_query("SELECT value FROM settings WHERE key = $1", SEND_ALERTS_SETTINGS_KEY).toBool()
if err != nil {
log.Fatal(err)
log.Fatal("Fatal Error on set alert:", err)
}
send_alerts = send_alerts_value
}
Expand Down
12 changes: 6 additions & 6 deletions database.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func initialize_settings() {
func initialize_users() {
new_password, err := get_secure_random_string(32)
if err != nil {
log.Fatal(err)
log.Fatal("Fatal Error Initialize Users:", err)
}

new_user := setup_admin_user(new_password)
Expand All @@ -189,7 +189,7 @@ func initialize_users() {
func setup_admin_user(password string) bool {
hashed_password, err := hash_string(password)
if err != nil {
log.Fatal(err)
log.Fatal("Fatal Error on Password Hash:", err)
}

return initialize_setting_helper(ADMIN_PASSWORD_SETTINGS_KEY, hashed_password)
Expand All @@ -198,28 +198,28 @@ func setup_admin_user(password string) bool {
func initialize_configs() {
session_secret, err := get_secure_random_string(64)
if err != nil {
log.Fatal(err)
log.Fatal("Fatal Error on Initialize Config:", err)
}
initialize_setting_helper(session_secret_key, session_secret)
}

func initialize_correlation_api() {
api_key, err := get_secure_random_string(64)
if err != nil {
log.Fatal(err)
log.Fatal("Fatal Error on Initialize Correlation Api:", err)
}
initialize_setting_helper(CORRELATION_API_SECRET_SETTINGS_KEY, api_key)
}

func initialize_setting_helper(key string, value string) bool {
has_setting, setting_err := db_single_item_query("SELECT 1 FROM settings WHERE key = $1", key).toBool()
if setting_err != nil {
log.Fatal(setting_err)
log.Fatal("Fatal Error on select setting helper:", setting_err)
}
if !has_setting {
_, err := db.Exec("INSERT INTO settings (key, value) VALUES ($1, $2)", key, value)
if err != nil {
log.Fatal(err)
log.Fatal("Fatal Error on insert setting helper:", err)
}
return false
}
Expand Down
6 changes: 3 additions & 3 deletions dbhelper.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ func initialize_sqlite_database() {
if _, err := os.Stat("db"); os.IsNotExist(err) {
err = os.MkdirAll("db", 0750)
if err != nil {
log.Fatal(err)
log.Fatal("Fatal Error on Initialize sqlite db:", err)
}
}
create_sqlite_tables()
Expand All @@ -222,15 +222,15 @@ func establish_sqlite_database_connection() *sql.DB {
dbPath := get_sqlite_database_path()
db, err := sql.Open("sqlite3", dbPath)
if err != nil {
log.Fatal(err)
log.Fatal("Fatal Error on connect to sqlite:", err)
}
return db
}

func establish_postgres_database_connection() *sql.DB {
db, err := sql.Open("postgres", get_env("DATABASE_URL"))
if err != nil {
log.Fatal(err)
log.Fatal("Fatal Error on connect to postgres:", err)
}
return db
}
50 changes: 30 additions & 20 deletions e2e/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,31 +36,41 @@ export async function clearCookies(context) {
await context.clearCookies({ domain: 'localhost' });
}

export async function triggerXSS(page, context, randomInjectionKey = "") {
export async function triggerXSS(page, context, randomInjectionKey = "", longPregeneratedHTML = "") {
await page.goto('about:blank');

await page.route('http://localhost:1449/', async (route) => {
const response = await route.fetch();
expect(response.status()).toBe(200);
route.continue();
});

await context.route('**/js_callback', async (route) => {
const response = await route.fetch();
expect(response.status()).toBe(200);
await route.continue();
});
// page.on('request', request => console.log('>>', request.method(), request.url()));
// page.on('response', response => console.log('<<', response.status(), response.url()));

const customHTML = `
<html>
<body>
<h1>Test</h1>
<script src='http://localhost:1449/${randomInjectionKey}'></script>
</body>
</html>
`;
<html>
<body>
<h1>Test XSS Payload</h1>
<script src='http://localhost:1449/${randomInjectionKey}'></script>
${longPregeneratedHTML}
</body>
</html>
`;

const responsePromise = page.waitForResponse('**/js_callback');

await page.setContent(customHTML);
await expect(page.getByText('Test')).toBeVisible();

await responsePromise;

await expect(page.getByText('Test XSS Payload')).toBeVisible();
}

export function generateHTML(length, lineBreakLength) {
let charOptions = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
let longPregeneratedHTML = "";

for (let i = 0; i < length; i++) {
longPregeneratedHTML += charOptions.charAt(Math.floor(Math.random() * charOptions.length));
if (i % lineBreakLength == 0) {
longPregeneratedHTML += "\n<br>\n";
}
}

return longPregeneratedHTML;
}
2 changes: 1 addition & 1 deletion e2e/playwright.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ module.exports = defineConfig({
trace: 'on-first-retry',
},

timeout: 20000,
timeout: 50000,

/* Configure projects for major browsers */
projects: [
Expand Down
36 changes: 34 additions & 2 deletions e2e/tests/xsshunter.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test';
import { clearCookies, login, navigateToPayloadImporterExporter, navigateToPayloadMaker, navigateToPayloads, navigateToSettings, triggerXSS } from '../helper';
import { clearCookies, login, navigateToPayloadImporterExporter, navigateToPayloadMaker, navigateToPayloads, navigateToSettings, triggerXSS, generateHTML } from '../helper';

const crypto = require('crypto');

Expand Down Expand Up @@ -99,4 +99,36 @@ test('Create Payload', async ({ page, context }) => {
await page.getByRole('button', { name: 'Payloads' }).click();
await expect(page.getByText(randomPayload + ` localhost:1449`)).toBeVisible();
await expect(page.getByText(randomTitle)).toBeVisible();
});
});

test('Basic Trigger XSS with a lot of HTML', async ({ page, context }) => {
await page.goto('http://localhost:1449/');
await clearCookies(context);

let skeletonHTML = "<div id='addtional-text'>";

let longPregeneratedHTML = generateHTML(200000, 250);

skeletonHTML += longPregeneratedHTML + "</div>";

await triggerXSS(page, context, "", skeletonHTML);

await page.waitForSelector('#addtional-text');
let substringToCheck = longPregeneratedHTML.slice(-100);
await expect(page.locator('#addtional-text')).toContainText(substringToCheck);

await page.goto('http://localhost:1449/admin');
await clearCookies(context);
});

test('Basic Trigger XSS with hidden HTML', async ({ page, context }) => {
await page.goto('http://localhost:1449/');
await clearCookies(context);

let longPregeneratedHTML = `<div id='addtional-text' style='display: none;'>${generateHTML(500000, 1000)}</div>`;

await triggerXSS(page, context, "", longPregeneratedHTML);

await page.goto('http://localhost:1449/admin');
await clearCookies(context);
});
30 changes: 18 additions & 12 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,18 +165,24 @@ type JSCallbackSchema struct {
func jscallbackHandler(w http.ResponseWriter, r *http.Request) {
set_callback_headers(w)

body, err := io.ReadAll(r.Body)
// Send the response immediately, they don't need to wait for us to store everything.
_, err := w.Write([]byte("OK"))
if err != nil {
fmt.Println(err)
return
fmt.Println("Error on write ok", err)
}

// Send the response immediately, they don't need to wait for us to store everything.
_, err = w.Write([]byte("OK"))
const MaxBodySize = 64 << 20 // 64MB

r.Body = http.MaxBytesReader(nil, r.Body, MaxBodySize)
buf := new(bytes.Buffer)
_, err = io.Copy(buf, r.Body)
if err != nil {
fmt.Println(err)
fmt.Println("Error on readbody: ", err)
return
}

body := buf.Bytes()

// Go routine to close the connection and process the data
go func(body []byte, ip_address string, host string) {
r := &http.Request{
Expand All @@ -187,7 +193,7 @@ func jscallbackHandler(w http.ResponseWriter, r *http.Request) {

err := r.ParseMultipartForm(32 << 20)
if err != nil {
fmt.Println(err)
fmt.Println("Error on parse multiform", err)
return
}

Expand All @@ -199,14 +205,14 @@ func jscallbackHandler(w http.ResponseWriter, r *http.Request) {
for _, file := range files {
fileContent, err := file.Open()
if err != nil {
fmt.Println(err)
fmt.Println("Error on open: ", err)
return
}
defer fileContent.Close()

newFile, err := os.Create(payload_fire_image_filename) // #nosec G304
if err != nil {
fmt.Println(err)
fmt.Println("Error on create", err)
return
}
defer newFile.Close()
Expand All @@ -216,15 +222,15 @@ func jscallbackHandler(w http.ResponseWriter, r *http.Request) {

_, err = io.Copy(gw, fileContent)
if err != nil {
fmt.Println(err)
fmt.Println("Error on copy", err)
return
}
}
}

err = r.ParseForm()
if err != nil {
log.Fatal(err)
log.Fatal("Error Parse form", err)
}

browser_time, _ := strconv.ParseUint(r.FormValue("browser-time"), 10, 64)
Expand Down Expand Up @@ -321,7 +327,7 @@ func probeHandler(w http.ResponseWriter, r *http.Request) {

_, errWrite := w.Write([]byte(xss_payload_4))
if errWrite != nil {
log.Fatal(err)
log.Fatal("Fatal Error on write payload:", err)
}
}

Expand Down
Loading

0 comments on commit 06e0d4c

Please sign in to comment.