From fcc3aa98fd744519c245162a85b9782aa1517aa3 Mon Sep 17 00:00:00 2001 From: ed Date: Sun, 16 Jul 2023 13:09:31 +0000 Subject: [PATCH] add path-traversal scanners --- scripts/test/ptrav.py | 73 ++++++++++++++++++++++++++++++++++++ tests/ptrav.py | 87 +++++++++++++++++++++++++++++++++++++++++++ tests/util.py | 9 +++-- 3 files changed, 166 insertions(+), 3 deletions(-) create mode 100755 scripts/test/ptrav.py create mode 100644 tests/ptrav.py diff --git a/scripts/test/ptrav.py b/scripts/test/ptrav.py new file mode 100755 index 00000000..59aa17a5 --- /dev/null +++ b/scripts/test/ptrav.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 + +import re +import sys +import time +import itertools +import requests + +atlas = ["%", "25", "2e", "2f", ".", "/"] + + +def genlen(ubase, port, ntot, nth, wlen): + n = 0 + t0 = time.time() + print("genlen %s nth %s port %s" % (wlen, nth, port)) + rsession = requests.Session() + ptn = re.compile(r"2.2.2.2|\.\.\.|///|%%%|\.2|/2./|%\.|/%/") + for path in itertools.product(atlas, repeat=wlen): + if "%" not in path: + continue + path = "".join(path) + if ptn.search(path): + continue + n += 1 + if n % ntot != nth: + continue + url = ubase % (port, path) + if n % 500 == nth: + spd = n / (time.time() - t0) + print(wlen, n, int(spd), url) + + try: + r = rsession.get(url) + except KeyboardInterrupt: + raise + except: + print("\n[=== RETRY ===]", url) + try: + r = rsession.get(url) + except: + r = rsession.get(url) + + if "fgsfds" in r.text: + with open("hit-%s.txt" % (time.time()), "w", encoding="utf-8") as f: + f.write(url) + raise Exception("HIT! {}".format(url)) + + +def main(): + ubase = sys.argv[1] + port = int(sys.argv[2]) + ntot = int(sys.argv[3]) + nth = int(sys.argv[4]) + for wlen in range(20): + genlen(ubase, port, ntot, nth, wlen) + + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass + +""" +python3 -m copyparty -v srv::r -p 3931 -q -j4 +nice python3 ./ptrav.py "http://127.0.0.1:%s/%sfa" 3931 3 0 +nice python3 ./ptrav.py "http://127.0.0.1:%s/%sfa" 3931 3 1 +nice python3 ./ptrav.py "http://127.0.0.1:%s/%sfa" 3931 3 2 +nice python3 ./ptrav2.py "http://127.0.0.1:%s/.cpr/%sfa" 3931 3 0 +nice python3 ./ptrav2.py "http://127.0.0.1:%s/.cpr/%sfa" 3931 3 1 +nice python3 ./ptrav2.py "http://127.0.0.1:%s/.cpr/%sfa" 3931 3 2 +(13x slower than /tests/ptrav.py) +""" diff --git a/tests/ptrav.py b/tests/ptrav.py new file mode 100644 index 00000000..a7c7a260 --- /dev/null +++ b/tests/ptrav.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 + +import re +import sys +import time +import itertools + +from . import util as tu +from .util import Cfg + +from copyparty.authsrv import AuthSrv +from copyparty.httpcli import HttpCli + +atlas = ["%", "25", "2e", "2f", ".", "/"] + + +def nolog(*a, **ka): + pass + + +def hdr(query): + h = "GET /{} HTTP/1.1\r\nCookie: cppwd=o\r\nConnection: close\r\n\r\n" + return h.format(query).encode("utf-8") + + +def curl(args, asrv, url, binary=False): + conn = tu.VHttpConn(args, asrv, nolog, hdr(url)) + HttpCli(conn).run() + if binary: + h, b = conn.s._reply.split(b"\r\n\r\n", 1) + return [h.decode("utf-8"), b] + + return conn.s._reply.decode("utf-8").split("\r\n\r\n", 1) + + +def genlen(ubase, ntot, nth, wlen): + args = Cfg(v=["s2::r"], a=["o:o", "x:x"]) + asrv = AuthSrv(args, print) + # h, ret = curl(args, asrv, "hey") + + n = 0 + t0 = time.time() + print("genlen %s nth %s" % (wlen, nth)) + ptn = re.compile(r"2.2.2.2|\.\.\.|///|%%%|\.2|/2./|%\.|/%/") + for path in itertools.product(atlas, repeat=wlen): + if "%" not in path: + continue + path = "".join(path) + if ptn.search(path): + continue + n += 1 + if n % ntot != nth: + continue + url = ubase + path + "fa" + if n % 500 == nth: + spd = n / (time.time() - t0) + print(wlen, n, int(spd), url) + + hdr, r = curl(args, asrv, url) + if "fgsfds" in r: + with open("hit-%s.txt" % (time.time()), "w", encoding="utf-8") as f: + f.write(url) + raise Exception("HIT! {}".format(url)) + + +def main(): + ubase = sys.argv[1] + ntot = int(sys.argv[2]) + nth = int(sys.argv[3]) + for wlen in range(20): + genlen(ubase, ntot, nth, wlen) + + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass + + +""" +nice pypy3 -m tests.ptrav "" 2 0 +nice pypy3 -m tests.ptrav "" 2 1 +nice pypy3 -m tests.ptrav .cpr 2 0 +nice pypy3 -m tests.ptrav .cpr 2 1 +(13x faster than /scripts/test/ptrav.py) +""" diff --git a/tests/util.py b/tests/util.py index 3aa53ab4..9f4e42df 100644 --- a/tests/util.py +++ b/tests/util.py @@ -32,7 +32,7 @@ if MACOS: from copyparty.__init__ import E from copyparty.__main__ import init_E -from copyparty.util import Unrecv, FHC +from copyparty.util import Unrecv, FHC, Garda init_E(E) @@ -98,7 +98,7 @@ class Cfg(Namespace): def __init__(self, a=None, v=None, c=None): ka = {} - ex = "daw dav_auth dav_inf dav_mac dav_rt dotsrch e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp ed emp force_js getmod grid hardlink ih ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_logues no_mv no_readme no_robots no_sb_md no_sb_lg no_scandir no_thumb no_vthumb no_zip nrand nw rand smb th_no_crop vc xdev xlink xvol" + ex = "daw dav_auth dav_inf dav_mac dav_rt dotsrch e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp ed emp force_js getmod grid hardlink ih ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_logues no_mv no_readme no_robots no_sb_md no_sb_lg no_scandir no_thumb no_vthumb no_zip nrand nw rand smb th_no_crop vague_403 vc ver xdev xlink xvol" ka.update(**{k: False for k in ex.split()}) ex = "dotpart no_rescan no_sendfile no_voldump plain_ip" @@ -113,7 +113,7 @@ class Cfg(Namespace): ex = "df loris re_maxage rproxy rsp_jtr rsp_slp s_wr_slp theme themes turbo" ka.update(**{k: 0 for k in ex.split()}) - ex = "ah_alg doctitle favico html_head lg_sbf log_fk md_sbf mth textfiles unlist R RS SR" + ex = "ah_alg doctitle favico html_head lg_sbf log_fk md_sbf mth name textfiles unlist R RS SR" ka.update(**{k: "" for k in ex.split()}) ex = "on403 on404 xad xar xau xban xbd xbr xbu xiu xm" @@ -176,6 +176,9 @@ class VHttpSrv(object): aliases = ["splash", "browser", "browser2", "msg", "md", "mde"] self.j2 = {x: J2_FILES for x in aliases} + self.gpwd = Garda("") + self.g404 = Garda("") + def cachebuster(self): return "a"