add module .dorks and flask.harden()
This commit is contained in:
parent
ee36616b43
commit
e5ca63953d
13 changed files with 65 additions and 14 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -24,3 +24,4 @@ dist/
|
||||||
.err
|
.err
|
||||||
.vscode
|
.vscode
|
||||||
/run.sh
|
/run.sh
|
||||||
|
ROADMAP.md
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
28
src/suou/dorks.py
Normal 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()
|
||||||
|
|
||||||
|
|
@ -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'
|
||||||
|
)
|
||||||
|
|
@ -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')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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',)
|
||||||
|
|
@ -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', )
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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')
|
||||||
|
|
@ -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')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Utilities for marshmallow, a schema-agnostic serializer/deserializer.
|
Miscellaneous validator closures.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue