add module .dorks and flask.harden()

This commit is contained in:
Yusur 2025-07-17 21:33:11 +02:00
parent ee36616b43
commit e5ca63953d
13 changed files with 65 additions and 14 deletions

1
.gitignore vendored
View file

@ -24,3 +24,4 @@ dist/
.err .err
.vscode .vscode
/run.sh /run.sh
ROADMAP.md

View file

@ -5,6 +5,7 @@
+ Added `ValueProperty`, abstract superclass for `ConfigProperty` + Added `ValueProperty`, abstract superclass for `ConfigProperty`
+ \[BREAKING] Changed the behavior of `makelist()`: now it's also a decorator, converting its return type to a list (revertable with `wrap=False`) + \[BREAKING] Changed the behavior of `makelist()`: now it's also a decorator, converting its return type to a list (revertable with `wrap=False`)
+ New module `lex` with functions `symbol_table()` and `lex()` — make tokenization more affordable + New module `lex` with functions `symbol_table()` and `lex()` — make tokenization more affordable
+ Add `dorks` module and `flask.harden()`
+ Added `addattr()` + Added `addattr()`
## 0.3.6 ## 0.3.6

View file

@ -26,6 +26,7 @@ from .classtools import Wanted, Incomplete
from .itertools import makelist, kwargs_prefix, ltuple, rtuple, additem from .itertools import makelist, kwargs_prefix, ltuple, rtuple, additem
from .i18n import I18n, JsonI18n, TomlI18n from .i18n import I18n, JsonI18n, TomlI18n
from .snowflake import Snowflake, SnowflakeGen from .snowflake import Snowflake, SnowflakeGen
from .lex import symbol_table, lex, ilex
__version__ = "0.4.0-dev28" __version__ = "0.4.0-dev28"
@ -36,7 +37,7 @@ __all__ = (
'SiqType', 'Snowflake', 'SnowflakeGen', 'StringCase', 'TomlI18n', 'Wanted', 'SiqType', 'Snowflake', 'SnowflakeGen', 'StringCase', 'TomlI18n', 'Wanted',
'additem', 'b2048decode', 'b2048encode', 'b32ldecode', 'b32lencode', 'additem', 'b2048decode', 'b2048encode', 'b32ldecode', 'b32lencode',
'b64encode', 'b64decode', 'cb32encode', 'cb32decode', 'count_ones', 'b64encode', 'b64decode', 'cb32encode', 'cb32decode', 'count_ones',
'deprecated', 'join_bits', 'jsonencode', 'kwargs_prefix', 'ltuple', 'deprecated', 'ilex', 'join_bits', 'jsonencode', 'kwargs_prefix', 'lex', 'ltuple',
'makelist', 'mask_shift', 'not_implemented', 'rtuple', 'split_bits', 'makelist', 'mask_shift', 'not_implemented', 'rtuple', 'split_bits',
'ssv_list', 'want_bytes', 'want_str' 'ssv_list', 'symbol_table', 'want_bytes', 'want_str'
) )

28
src/suou/dorks.py Normal file
View file

@ -0,0 +1,28 @@
"""
Web app hardening and PT utilities.
---
Copyright (c) 2025 Sakuragasaki46.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
See LICENSE for the specific language governing permissions and
limitations under the License.
This software is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
SENSITIVE_ENDPOINTS = """
/.git
/.gitignore
/node_modules
/wp-admin
/wp-login.php
/.ht
/package.json
/package-lock.json
/composer.
""".split()

View file

@ -14,8 +14,6 @@ This software is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
""" """
from .functools import deprecated
class MissingConfigError(LookupError): class MissingConfigError(LookupError):
""" """
Config variable not found. Config variable not found.
@ -42,3 +40,7 @@ class InconsistencyError(RuntimeError):
""" """
This program is in a state which it's not supposed to be in. This program is in a state which it's not supposed to be in.
""" """
__all__ = (
'MissingConfigError', 'MissingConfigWarning', 'LexError', 'InconsistencyError'
)

View file

@ -15,9 +15,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
""" """
from typing import Any from typing import Any
from flask import Flask, current_app, g, request from flask import Flask, abort, current_app, g, request
from .i18n import I18n from .i18n import I18n
from .configparse import ConfigOptions from .configparse import ConfigOptions
from .dorks import SENSITIVE_ENDPOINTS
def add_context_from_config(app: Flask, config: ConfigOptions) -> Flask: def add_context_from_config(app: Flask, config: ConfigOptions) -> Flask:
@ -66,6 +67,21 @@ def get_flask_conf(key: str, default = None, *, app: Flask | None = None) -> Any
app = current_app app = current_app
return app.config.get(key, default) return app.config.get(key, default)
__all__ = ('add_context_from_config', 'add_i18n', 'get_flask_conf') ## XXX UNTESTED!
def harden(app: Flask):
"""
Make common "dork" endpoints unavailable
"""
i = 1
for ep in SENSITIVE_ENDPOINTS:
@app.route(f'{ep}<path:rest>', name=f'unavailable_{i}')
def unavailable(rest):
abort(403)
i += 1
return app
# Optional dependency: do not import into __init__.py
__all__ = ('add_context_from_config', 'add_i18n', 'get_flask_conf', 'harden')

View file

@ -74,5 +74,5 @@ class Api(_Api):
super().__init__(*a, **ka) super().__init__(*a, **ka)
self.representations['application/json'] = output_json self.representations['application/json'] = output_json
# Optional dependency: do not import into __init__.py
__all__ = ('Api',) __all__ = ('Api',)

View file

@ -76,5 +76,5 @@ def require_auth(cls: type[DeclarativeBase], db: SQLAlchemy) -> Callable[Any, Ca
return auth_required return auth_required
# Optional dependency: do not import into __init__.py
__all__ = ('require_auth', ) __all__ = ('require_auth', )

View file

@ -15,14 +15,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
''' '''
from functools import wraps from functools import wraps
from typing import Any, Iterable, MutableMapping, TypeVar from typing import Any, Callable, Iterable, MutableMapping, TypeVar
import warnings import warnings
from suou.classtools import MISSING from suou.classtools import MISSING
_T = TypeVar('_T') _T = TypeVar('_T')
def makelist(l: Any, *, wrap: bool = True) -> list: def makelist(l: Any, *, wrap: bool = True) -> list | Callable[Any, list]:
''' '''
Make a list out of an iterable or a single value. Make a list out of an iterable or a single value.

View file

@ -52,6 +52,7 @@ def symbol_table(*args: Iterable[tuple | TokenSym], whitespace: str | None = Non
yield TokenSym('[' + re.escape(whitespace) + ']+', '', discard=True) yield TokenSym('[' + re.escape(whitespace) + ']+', '', discard=True)
symbol_table: Callable[..., list]
def ilex(text: str, table: Iterable[TokenSym], *, whitespace = False): def ilex(text: str, table: Iterable[TokenSym], *, whitespace = False):
""" """
@ -80,5 +81,6 @@ def ilex(text: str, table: Iterable[TokenSym], *, whitespace = False):
raise InconsistencyError raise InconsistencyError
i = mo.end(0) i = mo.end(0)
lex = makelist(ilex) lex: Callable[..., list] = makelist(ilex)
__all__ = ('symbol_table', 'lex', 'ilex')

View file

@ -117,6 +117,6 @@ class SiqField(Field):
def python_value(self, value: bytes) -> Siq: def python_value(self, value: bytes) -> Siq:
return Siq.from_bytes(value) return Siq.from_bytes(value)
# Optional dependency: do not import into __init__.py
__all__ = ('connect_reconnect', 'RegexCharField', 'SiqField') __all__ = ('connect_reconnect', 'RegexCharField', 'SiqField')

View file

@ -295,7 +295,7 @@ def require_auth_base(cls: type[DeclarativeBase], *, src: AuthSrc, column: str |
return wrapper return wrapper
return decorator return decorator
# Optional dependency: do not import into __init__.py
__all__ = ( __all__ = (
'IdType', 'id_column', 'entity_base', 'declarative_base', 'token_signer', 'match_column', 'match_constraint', 'IdType', 'id_column', 'entity_base', 'declarative_base', 'token_signer', 'match_column', 'match_constraint',
'author_pair', 'age_pair', 'require_auth_base', 'want_column' 'author_pair', 'age_pair', 'require_auth_base', 'want_column'

View file

@ -1,5 +1,5 @@
""" """
Utilities for marshmallow, a schema-agnostic serializer/deserializer. Miscellaneous validator closures.
--- ---