├── .gitignore ├── function.png ├── assets ├── dynamic-challenge-modal.js ├── dynamic-challenge-create.js ├── dynamic-challenge-update.js ├── dynamic-challenge-create.njk ├── dynamic-challenge-modal.njk └── dynamic-challenge-update.njk ├── README.md └── __init__.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[cod] 3 | -------------------------------------------------------------------------------- /function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CTFd/DynamicValueChallenge/HEAD/function.png -------------------------------------------------------------------------------- /assets/dynamic-challenge-modal.js: -------------------------------------------------------------------------------- 1 | window.challenge.renderer = new markdownit({ 2 | html: true, 3 | }); 4 | 5 | window.challenge.preRender = function () { 6 | 7 | }; 8 | 9 | window.challenge.render = function (markdown) { 10 | return window.challenge.renderer.render(markdown); 11 | }; 12 | 13 | 14 | window.challenge.postRender = function () { 15 | 16 | }; 17 | 18 | window.challenge.submit = function (cb, preview) { 19 | var chal_id = $('#chal-id').val(); 20 | var answer = $('#answer-input').val(); 21 | var nonce = $('#nonce').val(); 22 | 23 | var url = "/chal/"; 24 | if (preview) { 25 | url = "/admin/chal/"; 26 | } 27 | 28 | $.post(script_root + url + chal_id, { 29 | key: answer, 30 | nonce: nonce 31 | }, function (data) { 32 | cb(data); 33 | }); 34 | }; -------------------------------------------------------------------------------- /assets/dynamic-challenge-create.js: -------------------------------------------------------------------------------- 1 | // Markdown Preview 2 | $('#desc-edit').on('shown.bs.tab', function (event) { 3 | if (event.target.hash == '#desc-preview'){ 4 | var editor_value = $('#desc-editor').val(); 5 | $(event.target.hash).html( 6 | window.challenge.render(editor_value) 7 | ); 8 | } 9 | }); 10 | $('#new-desc-edit').on('shown.bs.tab', function (event) { 11 | if (event.target.hash == '#new-desc-preview'){ 12 | var editor_value = $('#new-desc-editor').val(); 13 | $(event.target.hash).html( 14 | window.challenge.render(editor_value) 15 | ); 16 | } 17 | }); 18 | $("#solve-attempts-checkbox").change(function() { 19 | if(this.checked) { 20 | $('#solve-attempts-input').show(); 21 | } else { 22 | $('#solve-attempts-input').hide(); 23 | $('#max_attempts').val(''); 24 | } 25 | }); 26 | 27 | $(document).ready(function(){ 28 | $('[data-toggle="tooltip"]').tooltip(); 29 | }); 30 | -------------------------------------------------------------------------------- /assets/dynamic-challenge-update.js: -------------------------------------------------------------------------------- 1 | $('#submit-key').click(function (e) { 2 | submitkey($('#chalid').val(), $('#answer').val()) 3 | }); 4 | 5 | $('#submit-keys').click(function (e) { 6 | e.preventDefault(); 7 | $('#update-keys').modal('hide'); 8 | }); 9 | 10 | $('#limit_max_attempts').change(function() { 11 | if(this.checked) { 12 | $('#chal-attempts-group').show(); 13 | } else { 14 | $('#chal-attempts-group').hide(); 15 | $('#chal-attempts-input').val(''); 16 | } 17 | }); 18 | 19 | // Markdown Preview 20 | $('#desc-edit').on('shown.bs.tab', function (event) { 21 | if (event.target.hash == '#desc-preview') { 22 | var editor_value = $('#desc-editor').val(); 23 | $(event.target.hash).html( 24 | window.challenge.render(editor_value) 25 | ); 26 | } 27 | }); 28 | $('#new-desc-edit').on('shown.bs.tab', function (event) { 29 | if (event.target.hash == '#new-desc-preview') { 30 | var editor_value = $('#new-desc-editor').val(); 31 | $(event.target.hash).html( 32 | window.challenge.render(editor_value) 33 | ); 34 | } 35 | }); 36 | 37 | function loadchal(id, update) { 38 | $.get(script_root + '/admin/chal/' + id, function(obj){ 39 | $('#desc-write-link').click(); // Switch to Write tab 40 | if (typeof update === 'undefined') 41 | $('#update-challenge').modal(); 42 | }); 43 | } 44 | 45 | function openchal(id){ 46 | loadchal(id); 47 | } 48 | 49 | $(document).ready(function(){ 50 | $('[data-toggle="tooltip"]').tooltip(); 51 | }); 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dynamic Value Challenges for CTFd 2 | 3 | #### NOTE: This plugin has been integrated into CTFd as of 2.0.0. Please file any issues at the [main CTFd repo](https://github.com/CTFd/CTFd) 4 | 5 | It's becoming commonplace in CTF to see challenges whose point values decrease 6 | after each solve. 7 | 8 | This CTFd plugin creates a dynamic challenge type which implements this 9 | behavior. Each dynamic challenge starts with an initial point value and then 10 | each solve will decrease the value of the challenge until a minimum point value. 11 | 12 | By reducing the value of the challenge on each solve, all users who have previously 13 | solved the challenge will have lowered scores. Thus an easier and more solved 14 | challenge will naturally have a lower point value than a harder and less solved 15 | challenge. 16 | 17 | Within CTFd you are free to mix and match regular and dynamic challenges. 18 | 19 | The current implementation requires the challenge to keep track of three values: 20 | 21 | * Initial - The original point valuation 22 | * Decay - The amount of solves before the challenge will be at the minimum 23 | * Minimum - The lowest possible point valuation 24 | 25 | The value decay logic is implemented with the following math: 26 | 27 | 34 | 35 |  36 | 37 | or in pseudo code: 38 | 39 | ``` 40 | value = (((minimum - initial)/(decay**2)) * (solve_count**2)) + initial 41 | value = math.ceil(value) 42 | ``` 43 | 44 | If the number generated is lower than the minimum, the minimum is chosen 45 | instead. 46 | 47 | A parabolic function is chosen instead of an exponential or logarithmic decay function 48 | so that higher valued challenges have a slower drop from their initial value. 49 | 50 | # Installation 51 | 52 | **REQUIRES: CTFd >= v1.2.0** 53 | 54 | 1. Clone this repository to `CTFd/plugins`. It is important that the folder is 55 | named `DynamicValueChallenge` so CTFd can serve the files in the `assets` 56 | directory. 57 | -------------------------------------------------------------------------------- /assets/dynamic-challenge-create.njk: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /assets/dynamic-challenge-modal.njk: -------------------------------------------------------------------------------- 1 |