mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-06-29 13:01:02 +00:00
124 lines
4.4 KiB
Python
124 lines
4.4 KiB
Python
#!/usr/bin/env python
|
|
|
|
"""
|
|
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
|
|
See the file 'LICENSE' for copying permission
|
|
|
|
Secure directory purge (lib/utils/purge.py, the --purge feature): multi-pass
|
|
overwrite + truncation + removal of a directory's content. Driven against a
|
|
throwaway temp tree so the real output dir is never touched.
|
|
"""
|
|
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from _testutils import bootstrap
|
|
bootstrap()
|
|
|
|
import lib.utils.purge as purge_mod
|
|
from lib.utils.purge import purge
|
|
|
|
|
|
class _RecordingLogger(object):
|
|
"""Captures every (level, message) emitted while installed as purge.logger."""
|
|
|
|
def __init__(self):
|
|
self.records = []
|
|
|
|
def _add(self, level):
|
|
return lambda msg, *a: self.records.append((level, msg % a if a else msg))
|
|
|
|
def __getattr__(self, name):
|
|
if name in ("warning", "info", "debug", "error", "critical"):
|
|
return self._add(name)
|
|
raise AttributeError(name)
|
|
|
|
def messages(self, level=None):
|
|
return [m for (lvl, m) in self.records if level is None or lvl == level]
|
|
|
|
|
|
class TestPurge(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp = tempfile.mkdtemp(prefix="sqlmap_purge_")
|
|
|
|
def tearDown(self):
|
|
shutil.rmtree(self.tmp, ignore_errors=True)
|
|
|
|
def test_overwrites_and_truncates_file_contents(self):
|
|
# a couple of files + a nested subdir with a (non-empty) file
|
|
plaintexts = {
|
|
os.path.join(self.tmp, "a.txt"): "secret data",
|
|
os.path.join(self.tmp, "sub", "b.txt"): "more secret data",
|
|
}
|
|
with open(os.path.join(self.tmp, "a.txt"), "w") as f:
|
|
f.write(plaintexts[os.path.join(self.tmp, "a.txt")])
|
|
with open(os.path.join(self.tmp, "empty.bin"), "w") as f:
|
|
pass
|
|
os.mkdir(os.path.join(self.tmp, "sub"))
|
|
with open(os.path.join(self.tmp, "sub", "b.txt"), "w") as f:
|
|
f.write(plaintexts[os.path.join(self.tmp, "sub", "b.txt")])
|
|
|
|
# neutralise the final rmtree so the overwrite/truncate work product remains
|
|
# observable on disk; the files are renamed, so locate them by walking the tree.
|
|
real_rmtree = purge_mod.shutil.rmtree
|
|
purge_mod.shutil.rmtree = lambda *a, **k: None
|
|
try:
|
|
purge(self.tmp)
|
|
finally:
|
|
purge_mod.shutil.rmtree = real_rmtree
|
|
|
|
# collect every surviving regular file (names are randomised by purge)
|
|
survivors = []
|
|
for root, _dirs, files in os.walk(self.tmp):
|
|
for name in files:
|
|
survivors.append(os.path.join(root, name))
|
|
|
|
# the originally non-empty files still exist (rmtree was a no-op) but the
|
|
# multi-pass overwrite + truncation reduced each to size 0 and the original
|
|
# plaintext is gone.
|
|
nonempty = [p for p in survivors if os.path.getsize(p) > 0]
|
|
self.assertEqual(nonempty, [], msg="files were not truncated to zero: %r" % nonempty)
|
|
|
|
blob = b"".join(open(p, "rb").read() for p in survivors)
|
|
for secret in plaintexts.values():
|
|
self.assertNotIn(secret.encode("utf-8"), blob,
|
|
msg="original plaintext %r survived the purge" % secret)
|
|
|
|
def test_purges_nested_content(self):
|
|
# full purge (including rmtree) wipes the whole tree
|
|
with open(os.path.join(self.tmp, "a.txt"), "w") as f:
|
|
f.write("secret data")
|
|
sub = os.path.join(self.tmp, "sub")
|
|
os.mkdir(sub)
|
|
with open(os.path.join(sub, "b.txt"), "w") as f:
|
|
f.write("more secret data")
|
|
|
|
purge(self.tmp)
|
|
|
|
self.assertFalse(os.path.exists(self.tmp))
|
|
|
|
def test_nonexistent_directory_is_noop(self):
|
|
missing = os.path.join(self.tmp, "does_not_exist")
|
|
|
|
real_logger = purge_mod.logger
|
|
rec = _RecordingLogger()
|
|
purge_mod.logger = rec
|
|
try:
|
|
# must not raise; the guard branch logs a skip warning and returns
|
|
purge(missing)
|
|
finally:
|
|
purge_mod.logger = real_logger
|
|
|
|
self.assertFalse(os.path.exists(missing))
|
|
self.assertTrue(
|
|
any("skipping purging" in w and "does not exist" in w for w in rec.messages("warning")),
|
|
msg="nonexistent-directory guard did not log its warning: %r" % rec.records,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|