本文为We Chall题解,仍在更新。
Training: Get Sourced
直接查看源代码。
答案为:html_sourcecode
Training: Stegno I
下载下来,打开二进制文件即可看到。
答案就是:steganoI
Training: Crypto - Caesar I
凯撒密码,移动 13 位,得到
THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG OF CAESAR AND YOUR UNIQUE SOLUTION IS FDLDGDFSHERH
答案为fdldgdfsherh
Training: WWW-Robots
这题没有提交按钮。
先访问 http://www.wechall.net/robots.txt 查看一下内容:
User-agent: *
Disallow: /challenge/training/www/robots/T0PS3CR3T
User-agent: Yandex
Disallow: *
/challenge/training/www/robots/T0PS3CR3T 是禁止爬虫爬取的,那么一定有问题。
访问网址即可:http://www.wechall.net/challenge/training/www/robots/T0PS3CR3T/
Training: ASCII
将ASCII转换为字符
结果为:The solution is: lpfnosrhcbrl
提交lpfnosrhcbrl即可。
Encodings: URL
URLDecode,得到:
Yippeh! Your URL is challenge/training/encodings/url/saw_lotion.php?p=dnserhciprci&cid=52#password=fibre_optics Very well done!
2021 Christmas Hippety
Prime Factory
枚举判断素数,懒得写了。结果是10000331000037
Training: Encodings I
JPK
题目提供了一个软件JPK,那就用它来解决。
1.看到0和1,猜测是二进制acsii转换。 ASCII码使用指定的7位或8位二进制数组合来表示128或256种可能的字符。
标准ASCII码也叫基础ASCII码,使用7位二进制数(剩下的1位二进制为0来表示所有的大写和小写字母,数字0到9、标点符号,以及在美式英语中使用的特殊控制字符。
2.用给定的JPK进行Binary Format转换,默认bitsperblock是8,转换后发现多出1位会乱码,换用7
3.最后转成ascii码,用给定的JPK进行Binary to ASCII转换即可。 This text is 7-bit encoded ascii. Your password is easystarter.
提交easystarter即可.
Python3
a='101010011010001101001111001101000001110100110010111110001110100010000011010011110011010000001101110101101110001011010011110100010000011001011101110110001111011111100100110010111001000100000110000111100111100011110100111010010101110010000010110011101111111010111100100100000111000011000011110011111001111101111101111111001011001000100000110100111100110100000110010111000011110011111100111100111110100110000111100101110100110010111100100101110'
for i in range(0,len(a),7):
print(chr(int(a[i:i+7],2)),end="")
JavaScript
var a = '101010011010001101001111001101000001110100110010111110001110100010000011010011110011010000001101110101101110001011010011110100010000011001011101110110001111011111100100110010111001000100000110000111100111100011110100111010010101110010000010110011101111111010111100100100000111000011000011110011111001111101111101111111001011001000100000110100111100110100000110010111000011110011111100111100111110100110000111100101110100110010111100100101110'
var text = ''
for (var i = 0; i < a.length; i+=7) {
text += String.fromCharCode(parseInt(a.substr(i, 7), 2))
}
console.log(text)
PHP
<?php
function bin2ascii($texto){
$tmp = '';
$texto = preg_replace("/[^01]*/", '', $texto);
for($i=0; $i<strlen($texto); $i += 7){
$tmp .= chr(bindec(substr($texto, $i, 7)));
}
return $tmp;
}
$txt = '101010011010001101001111001101000001110100110010111110001110100010000011010011110011010000001101110101101110001011010011110100010000011001011101110110001111011111100100110010111001000100000110000111100111100011110100111010010101110010000010110011101111111010111100100100000111000011000011110011111001111101111101111111001011001000100000110100111100110100000110010111000011110011111100111100111110100110000111100101110100110010111100100101110';
echo bin2ascii($txt);
?>
C
首先需要实现二进制转十进制函数,这里参考了C语言二进制转化为十进制源码,之后用了strncpy函数提取7个字符。
#include <math.h>
#include <stdio.h>
#include <string.h>
int bin2dec(char a[])
{
int n, sum = 0, i = 0;
n = strlen(a);
for (i = n - 1; i >= 0; i--)
sum += (a[i] - '0') * ((int)pow(2, n - 1 - i));
return sum;
}
int main()
{
int sum = 0;
char txt[1024] = "101010011010001101001111001101000001110100110010111110001110100010000011010011110011010000001101110101101110001011010011110100010000011001011101110110001111011111100100110010111001000100000110000111100111100011110100111010010101110010000010110011101111111010111100100100000111000011000011110011111001111101111101111111001011001000100000110100111100110100000110010111000011110011111100111100111110100110000111100101110100110010111100100101110";
char temp[1024];
for (int i = 0; i < strlen(txt); i+=7)
{
strncpy(temp, txt+i, 7);
sum = bin2dec(temp);
printf("%c", sum);
}
}
Bash
这个方法来自[WeChall] Solution in Bash
其中$((2#$a))的意思是: 将2进制转成10进制 printf \\是转义反斜杠,来显示八进制数的ascii码。
ascii=$(echo "101010011010001101001111001101000001110100110010111110001110100010000011010011110011010000001101110101101110001011010011110100010000011001011101110110001111011111100100110010111001000100000110000111100111100011110100111010010101110010000010110011101111111010111100100100000111000011000011110011111001111101111101111111001011001000100000110100111100110100000110010111000011110011111100111100111110100110000111100101110100110010111100100101110" | tr -d '\n' | sed -r 's/(.{7})/\1\n/g')
for a in $ascii; do printf \\$(printf "%o" $(echo $((2#$a)))); done; echo
Training: Programming 1
Python
import requests
url1 = "http://www.wechall.net/challenge/training/programming1/index.php?action=request"
url2 = "http://www.wechall.net/challenge/training/programming1/index.php?answer="
c = {"WC": "你的cookie"}
key = requests.get(url1, cookies=c).text
requests.get(url2+key, cookies=c)
Curl
answer=$(curl -H 'Cookie:WC=你的cookie' http://www.wechall.net/challenge/training/programming1/index.php?action=request)
curl -H 'Cookie:WC=你的cookie' http://www.wechall.net/challenge/training/programming1/index.php?answer=$answer
Training: Regex
Level 1
匹配一个空字符串,学习匹配匹配字符串开头结尾的两个符号:/^$/
^ 匹配字符串的开始
$ 匹配字符串的结束
Level 2
匹配”wechall”,/^wechall$/
Level 3
匹配以wechall或wechall4为文件名,并以.jpg/.gif/.tiff/.bmp/.png为后缀的图像,
/^wechall4?\.(?:jpg|gif|tiff|bmp|png)$/
? 重复零次或一次
所以4?表示重复0次或1次4,也就是wechall和wechall4都可以匹配。
\. 转义
使用\来取消.字符的特殊意义,来显示.字符本身
(?:jpg|gif|tiff|bmp|png)\
(?:exp)表示非捕获分组,匹配exp,不捕获匹配的文本,也不给此分组分配组号。
为什么要用(?:exp),而不用(exp)呢?因为直接提交/^wechall4?\.(jpg|gif|tiff|bmp|png)$/会报错:
Your pattern would capture a string, but this is not wanted. Please use a non capturing group. 您的模式将捕获一个字符串,但这是不需要的。请使用非捕获组。
所以需要使用(?:exp)非捕获分组。
至于(?:jpg|gif|tiff|bmp|png)\中的|表示分枝条件
Level 4
捕获文件名,需要对文件名添加捕获分组:
/^(wechall4?)\.(?:jpg|gif|tiff|bmp|png)$/
(wechall4?)用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作。
Training: PHP LFI
这是关于利用文件包含漏洞的题目。 目标是执行../solution.php文件。
题目的网址是:http://www.wechall.net/challenge/training/php/lfi/up/index.php
复制一下../solution.php的地址,可以看到是http://www.wechall.net/challenge/training/php/lfi/solution.php
所以应该是../../ 而不是 ../, 因为从index.php跳转到solution.php需要经过两个父目录。
但是直接提交?file=../../solution.php会出错,提示找不到文件pages/../../solution.php.html
可以看到后面多了个.html,可以利用空字节%00来过滤掉后面的.html
最终只要浏览器访问下面地址,即可解题成功
http://www.wechall.net/challenge/training/php/lfi/up/index.php?file=../../solution.php%00
PHP 0817
当一个非数字开头的字符串与数字0进行==比较时,结果总是true.因此可以直接提交solution作为which变量的值,"solution"相当于0,必然会执行require_once命令。
更多知识可以参考字符串与数字0比较要注意
因此,答案也就出来了。浏览器访问下面链接即可。
http://www.wechall.net/challenge/php0817/index.php?which=solution
Training: Crypto - Transposition I
置换密码就是对明文重新排序以形成密文。
大致过程为:加密,先分组(最后不足补齐);分别按组进行置换(置换矩阵)。
对待置换密码,首先需要根据其长度特征进行判断分组大小。分组大小是密文长度的因子。
该文本的长度为148,对148求因数是2,2,37。 也就是说分组大小可能是2,2,37。就是说是有4 x 37, 37 x 4, 2 x 74, 74 x 2这么几种情况。
可以用Transposition Cipher Solver来将密码转成矩阵形式。
可以看到:将每两个字符(矩阵的每行)调换一下顺序,就可以还原成明文。例如oWdnreuf.l就是Wonderful.
python
def decrypto(crypto):
for i in range(0, len(crypto),2):
print(crypto[i+1], end="")
print(crypto[i], end="")
print()
decrypto("oWdnreuf.lY uoc nar ae dht eemssga eaw yebttrew eh nht eelttre sra enic roertco drre . Ihtni koy uowlu dilekt oes eoyrup sawsro don:wo nnibhmfsoo.r")
运行结果:
Wonderful. You can read the message way better when the letters are in correct order. I think you would like to see your password now: onnbimhsfoor.
提交onnbimhsfoor即可。
Bash
创建ciphertext文件,内容为
oWdnreuf.lY uoc nar ae dht eemssga eaw yebttrew eh nht eelttre sra enic roertco drre . Ihtni koy uowlu dilekt oes eoyrup sawsro don:wo nnibhmfsoo.r
接着在terminal输入下面命令
sed -r -e 's/(.{2})/\1\n/g' ciphertext | sed -r -e 's/(.)(.)/\2\1/g' | tr -d '\n'; echo
PHP
<?php
function crypto_trans1_encrypt($pt)
{
$len = strlen($pt);
if (($len % 2) == 1) {
$pt .= 'X';
$len++;
}
$i = 0;
$ct = '';
while ($i < $len) {
$ct .= $pt{$i + 1};
$ct .= $pt{$i};
$i += 2;
}
return $ct;
}
$ciphertext = "oWdnreuf.lY uoc nar ae dht eemssga eaw yebttrew eh nht eelttre sra enic roertco drre . Ihtni koy uowlu dilekt oes eoyrup sawsro don:wo nnibhmfsoo.r";
$plaintext = crypto_trans1_encrypt($ciphertext);
print($plaintext);
?>
JavaScript
var answer = "oWdnreuf.lY uoc nar ae dht eemssga eaw yebttrew eh nht eelttre sra enic roertco drre . Ihtni koy uowlu dilekt oes eoyrup sawsro don:wo lgnhipasmi.m"
.match(/[\s\S]{1,2}/g)
.map(function(val){
return val[1]+val[0];
})
.join('');
console.log(answer)
C
如果用在其他地方,malloc(200);中的200可以改大点,以容纳更多字符。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char* decrypt(char cryptotext[]){
int i = 0;
char *value = malloc(200);
while (i < strlen(cryptotext)){
value[i] = cryptotext[i+1];
value[i+1] = cryptotext[i];
i += 2;
}
value[i] = '\0';
return value;
}
int main(void)
{
char cryptotext[] = "oWdnreuf.lY uoc nar ae dht eemssga eaw yebttrew eh nht eelttre sra enic roertco drre . Ihtni koy uowlu dilekt oes eoyrup sawsro don:wo nnibhmfsoo.r";
printf("%s\n", decrypt(cryptotext));
return 0;
}
Training: Crypto - Substitution I
题目中提到了 simple substitution 中文叫单表替代密码
中文原理可以参考这篇文章如何破解单表替换密码
这里使用在线工具来解决,quipquip。粘贴密文进去,点击Solve即可。
破解后的文本是
BY THE ALMIGHTY GOD YOU CAN READ THIS MY FRIEND I AM IMPRESSED VERY WELL DONE YOUR SOLUTION KEY IS FALFONNFAOFN THIS LITTLE CHALLENGE WAS NOT TOO HARD WAS IT
提交FALFONNFAOFN即可。
Training: Crypto - Caesar II
根据题目可以看到,明文不再是简单的26个字母,而是数字、字符组成的ascii序列。
从给的ascii码值中可以看到是16位进制数值。
首先把题目给的acsii码中换行符换成空格,在Linux中可以使用命令
$ echo "37 5F 5F 54 20 5A 5F 52 1C 20 69 5F 65 20 63 5F
5C 66 55 54 20 5F 5E 55 20 5D 5F 62 55 20 53 58
51 5C 5C 55 5E 57 55 20 59 5E 20 69 5F 65 62 20
5A 5F 65 62 5E 55 69 1E 20 44 58 59 63 20 5F 5E
55 20 67 51 63 20 56 51 59 62 5C 69 20 55 51 63
69 20 64 5F 20 53 62 51 53 5B 1E 20 47 51 63 5E
17 64 20 59 64 2F 20 21 22 28 20 5B 55 69 63 20
59 63 20 51 20 61 65 59 64 55 20 63 5D 51 5C 5C
20 5B 55 69 63 60 51 53 55 1C 20 63 5F 20 59 64
20 63 58 5F 65 5C 54 5E 17 64 20 58 51 66 55 20
64 51 5B 55 5E 20 69 5F 65 20 64 5F 5F 20 5C 5F
5E 57 20 64 5F 20 54 55 53 62 69 60 64 20 64 58
59 63 20 5D 55 63 63 51 57 55 1E 20 47 55 5C 5C
20 54 5F 5E 55 1C 20 69 5F 65 62 20 63 5F 5C 65
64 59 5F 5E 20 59 63 20 55 57 51 58 5F 60 5F 51
53 54 60 52 1E" | tr '\n' ' '
从而得到:
37 5F 5F 54 20 5A 5F 52 1C 20 69 5F 65 20 63 5F 5C 66 55 54 20 5F 5E 55 20 5D 5F 62 55 20 53 58 51 5C 5C 55 5E 57 55 20 59 5E 20 69 5F 65 62 20 5A 5F 65 62 5E 55 69 1E 20 44 58 59 63 20 5F 5E 55 20 67 51 63 20 56 51 59 62 5C 69 20 55 51 63 69 20 64 5F 20 53 62 51 53 5B 1E 20 47 51 63 5E 17 64 20 59 64 2F 20 21 22 28 20 5B 55 69 63 20 59 63 20 51 20 61 65 59 64 55 20 63 5D 51 5C 5C 20 5B 55 69 63 60 51 53 55 1C 20 63 5F 20 59 64 20 63 58 5F 65 5C 54 5E 17 64 20 58 51 66 55 20 64 51 5B 55 5E 20 69 5F 65 20 64 5F 5F 20 5C 5F 5E 57 20 64 5F 20 54 55 53 62 69 60 64 20 64 58 59 63 20 5D 55 63 63 51 57 55 1E 20 47 55 5C 5C 20 54 5F 5E 55 1C 20 69 5F 65 62 20 63 5F 5C 65 64 59 5F 5E 20 59 63 20 55 57 51 58 5F 60 5F 51 53 54 60 52 1E
构造python3代码:
cipher = "37 5F 5F 54 20 5A 5F 52 1C 20 69 5F 65 20 63 5F 5C 66 55 54 20 5F 5E 55 20 5D 5F 62 55 20 53 58 51 5C 5C 55 5E 57 55 20 59 5E 20 69 5F 65 62 20 5A 5F 65 62 5E 55 69 1E 20 44 58 59 63 20 5F 5E 55 20 67 51 63 20 56 51 59 62 5C 69 20 55 51 63 69 20 64 5F 20 53 62 51 53 5B 1E 20 47 51 63 5E 17 64 20 59 64 2F 20 21 22 28 20 5B 55 69 63 20 59 63 20 51 20 61 65 59 64 55 20 63 5D 51 5C 5C 20 5B 55 69 63 60 51 53 55 1C 20 63 5F 20 59 64 20 63 58 5F 65 5C 54 5E 17 64 20 58 51 66 55 20 64 51 5B 55 5E 20 69 5F 65 20 64 5F 5F 20 5C 5F 5E 57 20 64 5F 20 54 55 53 62 69 60 64 20 64 58 59 63 20 5D 55 63 63 51 57 55 1E 20 47 55 5C 5C 20 54 5F 5E 55 1C 20 69 5F 65 62 20 63 5F 5C 65 64 59 5F 5E 20 59 63 20 55 57 51 58 5F 60 5F 51 53 54 60 52 1E"
cipher = cipher.split()
#对整个字符串循环
for shift in range(127):
#遍历字符串的每个字符
for every in cipher:
current = int(every, 16)+shift+1
print(chr(current % 128), end='')
print()
print(shift+1)
在16的时候,得到flag
Good0job,0you0solved0one0more0challenge0in0your0journey.0This0one0was0fairly0easy0to0crack.0Wasn't0it?01280keys0is0a0quite0small0keyspace,0so0it0shouldn't0have0taken0you0too0long0to0decrypt0this0message.0Well0done,0your0solution0is0egahopoacdpb.
16
把0换成空格,来看下
Good job, you solved one more challenge in your journey. This one was fairly easy to crack. Wasn't it? 128 keys is a quite small keyspace, so it shouldn't have taken you too long to decrypt this message. Well done, your solution is egahopoacdpb.
提交egahopoacdpb即可。
Training: Crypto - Digraphs
Training: MySQL I
题目已给判断登陆成功代码,第42行定义了查询语句。
验证代码
$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";
方法一
username输入 admin'# 即可登录。
原理:用Mysql单行注释符号#将后半句AND语句注释掉。
$query = "SELECT * FROM users WHERE username='admin'#' AND password='$password'";
构成SQL语句
SELECT * FROM users WHERE username='admin'
故而登录成功。 补充:之所以不是php注释,是因为#在双引号中,只会显示它的字面量。
同样可以用--–的后面要加空格,详情参考MySQL的注释 - 简书
方法二
username输入 admin' or '1 即可登录。
构成SQL语句
SELECT * FROM users WHERE username='admin' or '1' AND password='$password'
这里的1可以换成其他非空字符。
因为or在and的前面,所以可以理解成username='admin'和 '1' AND password='$password' 进行或运算, 因此即使第二部分是假,整条语句也是真的。
Training: MySQL II
从代码可以看到username password分开来验证。通常的利用方法是使用union构造已知MD5值的查询。
查询代码:
$query = "SELECT * FROM users WHERE username='$username'";
username一栏填写123' union select 1,'admin',md5('password');#
构成sql语句:
SELECT * FROM users WHERE username='123' union select 1,'admin',md5('password');#'
由于最后有个注释符号,所以相当于:
SELECT * FROM users WHERE username='123' union select 1,'admin',md5('password');
这句话首先通过username=123将原语句报错。因此返回的将会是第二条语句产生的信息。
而我们union select的是直接构造了用户名为admin,密码为password的md5值。这样就可以让程序误认为我们构造的信息就是它从数据库里面提取得到的信息。
验证密码正确是通过判断: $result['password'] 和 $password 是否一致。
$result['password']是用union构造的,因此password一栏填写password即可登录成功。
username填写 123' union select 1,'admin',md5('password');# password填写 password
Training: WWW-Basics
Training: Register Globals
这题和 mysql I 那题很类似,但是由于加了一个判断:
if (strtolower($login[0]) === 'admin') {
$chall->onChallengeSolved(GWF_Session::getUserID());
}
判断中的$login[0]在28行已经被赋值为$_POST['username']。
所以不能通过将username的值提交成admin'#来完成这题。
从32行开始,有下面这段代码:
if (isset($login))
{
echo GWF_HTML::message('Register Globals', $chall->lang('msg_welcome_back', array(htmlspecialchars($login[0]), htmlspecialchars($login[1]))));
if (strtolower($login[0]) === 'admin') {
$chall->onChallengeSolved(GWF_Session::getUserID());
}
}
意思是:如果$login[0] === 'admin',那么解题成功。
由于在28行已经有下面代码:
$login = array($_POST['username'], (int)$row['level']);
因而,$login[0]已经被赋值为$_POST['username'],但是可以通过全局变量来覆盖它。
在这里,程序会将GET等语句得到的变量注册成为全局变量,就可以覆盖掉源代码中的变量值。
因此在地址栏输入下面链接,回车即可成功解题
http://www.wechall.net/challenge/training/php/globals/globals.php?login[0]=admin
Training: Math Pyramid
题目要求: 求出方锥体积公式,公式所用字符不得超过9。
方锥体积是:
\[V=\frac{1}{3}SH=\frac{1}{3}a^2 H\]有红线和蓝线所表示的两种求法。
根据蓝线,有下面等式:
\[H^2+{\left(\frac{a}{2}\right)}^2=a^2-{\left(\frac{a}{2}\right)}^2\]进而得到
\[H=\sqrt{a^2 - 2\times{\left(\frac{a}{2}\right)}^2}=\sqrt{\frac{a^2}{2}}=\frac{a}{\sqrt{2}}=\frac{\sqrt{2}a}{2}\]带入体积计算公式:
\[V=\frac{1}{3}a^2H=\frac{a^3}{3\sqrt{2}}\]可以写成a^3/3/sqrt(2),但是这样就超过了9个字符限制,所以可以写成a^3/18^.5,提交即可。
Training: Baconian
Training: LSB
Training: GPG
Limited Access
PHP
<?php
$cookie="WC=xxxxxxxxxxxxxxxxxxxxx";
$url="http://www.wechall.net/challenge/wannabe7331/limited_access/protected/protected.php";
$post="";
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_COOKIE, $cookie);
curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
$res = curl_exec ($ch);
curl_close($ch);
echo $res;
?>
HTML
新建html文件,填写以下内容。通过模拟表单提交来提交post请求。
<form method="POST" action="https://www.wechall.net/challenge/wannabe7331/limited_access/protected/protected.php">
<input type="submit" value="submit">
</form>
Wget
wget --post-data=limited --header "Cookie: WC=*" http://www.wechall.net/challenge/wannabe7331/limited_access/protected/protected.php
Curl
curl -v -H "Cookie: [...]" -X POST http://www.wechall.net/challenge/wannabe7331/limited_access/protected/protected.php
Comments